From f1c09e76f24f358ee924c59279d3b4c3ba464e73 Mon Sep 17 00:00:00 2001 From: Plamen Dimitrov Date: Mon, 16 Oct 2023 22:46:12 +0300 Subject: [PATCH 01/15] Unify all logfile handling into utils_logfile Most importantly, for better decoupling with aexpect we provide sessions with close hooks that should have the same effect of closing the open log file descriptors but in a more decoupled fashion. The logging functionality in utils_misc has been fully deprecated for the sake of the same functionality in a fully devoted module. Signed-off-by: Plamen Dimitrov --- avocado_vt/test.py | 3 +- virttest/ip_sniffing.py | 2 +- virttest/libvirt_vm.py | 13 +++-- virttest/qemu_devices/qcontainer.py | 3 +- virttest/qemu_devices/qdevices.py | 5 +- virttest/qemu_io.py | 3 +- virttest/qemu_monitor.py | 4 +- virttest/qemu_vm.py | 9 ++- virttest/remote.py | 3 +- virttest/test_setup/__init__.py | 3 +- virttest/utils_logfile.py | 26 ++++++--- virttest/utils_misc.py | 87 ++++++----------------------- virttest/virt_vm.py | 14 +++-- 13 files changed, 74 insertions(+), 101 deletions(-) diff --git a/avocado_vt/test.py b/avocado_vt/test.py index c306cb96a9c..67ed2477fa5 100644 --- a/avocado_vt/test.py +++ b/avocado_vt/test.py @@ -31,6 +31,7 @@ from virttest import funcatexit from virttest import utils_env from virttest import utils_params +from virttest import utils_logfile from virttest import utils_misc from virttest import version from virttest._wrappers import load_source @@ -111,7 +112,7 @@ def __init__(self, **kwargs): self.__vt_params = vt_params self.debugdir = self.logdir self.resultsdir = self.logdir - utils_misc.set_log_file_dir(self.logdir) + utils_logfile.set_log_file_dir(self.logdir) self.__status = None self.__exc_info = None diff --git a/virttest/ip_sniffing.py b/virttest/ip_sniffing.py index 5c711f21fd7..53afb4d023e 100644 --- a/virttest/ip_sniffing.py +++ b/virttest/ip_sniffing.py @@ -18,7 +18,7 @@ import six -from virttest.utils_misc import log_line +from virttest.utils_logfile import log_line from virttest.utils_version import VersionInterval LOG = logging.getLogger('avocado.' + __name__) diff --git a/virttest/libvirt_vm.py b/virttest/libvirt_vm.py index 8592fab987c..9284654ab90 100644 --- a/virttest/libvirt_vm.py +++ b/virttest/libvirt_vm.py @@ -23,6 +23,7 @@ from avocado.core import exceptions from virttest import error_context +from virttest import utils_logfile from virttest import utils_misc from virttest import cpu from virttest import virt_vm @@ -1455,9 +1456,10 @@ def _create_serial_console(self): cmd += (" console %s %s" % (self.name, self.serial_ports[0])) except IndexError: raise virt_vm.VMConfigMissingError(self.name, "serial") - output_func = utils_misc.log_line # Because qemu-kvm uses this + output_func = utils_logfile.log_line # Because qemu-kvm uses this + port = self.serial_ports[0] # Because qemu-kvm hard-codes this - output_filename = self.get_serial_console_filename(self.serial_ports[0]) + output_filename = self.get_serial_console_filename(port) output_params = (output_filename,) prompt = self.params.get("shell_prompt", "[\#\$]") LOG.debug("Command used to create serial console: %s", cmd) @@ -1467,10 +1469,11 @@ def _create_serial_console(self): prompt=prompt) if not self.serial_console.is_alive(): LOG.error("Failed to create serial_console") - # Cause serial_console.close() to close open log file - self.serial_console.set_log_file(output_filename) - self.serial_console_log = os.path.join(utils_misc.get_log_file_dir(), + self.serial_console_log = os.path.join(utils_logfile.get_log_file_dir(), output_filename) + # Cause serial_console.close() to close open log file + self.serial_console.set_log_file(self.serial_console_log) + self.serial_console.close_hooks += [utils_logfile.close_own_log_file] def set_root_serial_console(self, device, remove=False): """ diff --git a/virttest/qemu_devices/qcontainer.py b/virttest/qemu_devices/qcontainer.py index 8bc3970f653..5cd3e03f4d4 100644 --- a/virttest/qemu_devices/qcontainer.py +++ b/virttest/qemu_devices/qcontainer.py @@ -34,6 +34,7 @@ # Internal imports from virttest import vt_iothread from virttest import utils_qemu +from virttest import utils_logfile from virttest import utils_misc from virttest import arch, storage, data_dir, virt_vm from virttest import qemu_storage @@ -3174,7 +3175,7 @@ def tpm_define_by_params(self, name, params): def _handle_log(line): try: - utils_misc.log_line('%s_swtpm_setup.log' % name, line) + utils_logfile.log_line('%s_swtpm_setup.log' % name, line) except Exception as e: LOG.warn("Can't log %s_swtpm_setup output: %s.", name, e) diff --git a/virttest/qemu_devices/qdevices.py b/virttest/qemu_devices/qdevices.py index 9b97ebc6f16..5691f15aac6 100644 --- a/virttest/qemu_devices/qdevices.py +++ b/virttest/qemu_devices/qdevices.py @@ -18,6 +18,7 @@ import aexpect from virttest import qemu_monitor, utils_numeric +from virttest import utils_logfile from virttest import utils_misc from virttest.qemu_devices.utils import DeviceError from virttest.qemu_devices.utils import none_or_int @@ -2082,7 +2083,7 @@ def _handle_log(self, line): """Handle the log of virtiofs daemon.""" name = self.get_param('name') try: - utils_misc.log_line('%s-%s.log' % (self.get_qid(), name), line) + utils_logfile.log_line('%s-%s.log' % (self.get_qid(), name), line) except Exception as e: LOG.warn("Can't log %s-%s, output: '%s'.", self.get_qid(), name, e) @@ -2155,7 +2156,7 @@ def start_daemon(self): if self.get_param('version') in ('2.0', ): tpm_cmd += ' --tpm2' - log_dir = utils_misc.get_log_file_dir() + log_dir = utils_logfile.get_log_file_dir() log_file = os.path.join(log_dir, '%s_swtpm.log' % self.get_qid()) Path(log_file).touch(mode=0o644, exist_ok=True) tpm_cmd += ' --log file=%s' % log_file diff --git a/virttest/qemu_io.py b/virttest/qemu_io.py index 829cb556552..22f545ab94b 100644 --- a/virttest/qemu_io.py +++ b/virttest/qemu_io.py @@ -3,6 +3,7 @@ import aexpect from avocado.utils import process +from virttest import utils_logfile from virttest import utils_misc from virttest import error_context @@ -27,7 +28,7 @@ def __init__(self, test, params, image_name, blkdebug_cfg="", self.type = "" if log_filename: log_filename += "-" + utils_misc.generate_random_string(4) - self.output_func = utils_misc.log_line + self.output_func = utils_logfile.log_line self.output_params = (log_filename,) else: self.output_func = None diff --git a/virttest/qemu_monitor.py b/virttest/qemu_monitor.py index 25d149ed1e3..1b8a84d18e7 100644 --- a/virttest/qemu_monitor.py +++ b/virttest/qemu_monitor.py @@ -18,6 +18,7 @@ import six +from . import utils_logfile from . import utils_misc from . import cartesian_config from . import data_dir @@ -449,8 +450,7 @@ def _log_lines(self, log_str): raise MonitorLockError("Could not acquire exclusive lock to access" " %s" % self.open_log_files) try: - log_file_dir = utils_misc.get_log_file_dir() - log = utils_misc.get_path(log_file_dir, self.log_file) + log = utils_logfile.get_log_filename(self.log_file) timestr = time.strftime("%Y-%m-%d %H:%M:%S") try: if log not in self.open_log_files: diff --git a/virttest/qemu_vm.py b/virttest/qemu_vm.py index 6e3d4aa7fd6..2ef256721ba 100644 --- a/virttest/qemu_vm.py +++ b/virttest/qemu_vm.py @@ -33,6 +33,7 @@ import six from six.moves import xrange +from virttest import utils_logfile from virttest import utils_misc from virttest import utils_qemu from virttest import cpu @@ -2929,7 +2930,7 @@ def _create_serial_console(self): self.serial_console = aexpect.ShellSession( "nc -U %s" % file_name, auto_close=False, - output_func=utils_misc.log_line, + output_func=utils_logfile.log_line, output_params=(log_name,), prompt=self.params.get("shell_prompt", "[\#\$]"), status_test_command=self.params.get("status_test_command", @@ -3526,13 +3527,15 @@ def create(self, name=None, params=None, root_dir=None, self.create_serial_console() for key, value in list(self.logs.items()): - outfile = "%s-%s.log" % (key, name) + outfile = os.path.join(utils_logfile.get_log_file_dir(), + "%s-%s.log" % (key, name)) self.logsessions[key] = aexpect.Tail( "nc -U %s" % value, auto_close=False, - output_func=utils_misc.log_line, + output_func=utils_logfile.log_line, output_params=(outfile,)) self.logsessions[key].set_log_file(outfile) + self.logsessions[key].close_hooks += [utils_logfile.close_own_log_file] # Wait for IO channels setting up completely, # such as serial console. diff --git a/virttest/remote.py b/virttest/remote.py index d2ed24f4c37..9e4eabfbf66 100644 --- a/virttest/remote.py +++ b/virttest/remote.py @@ -86,7 +86,8 @@ def remote_commander(client, host, port, username, password, prompt, log_file = utils_logfile.get_log_filename(log_filename) session.set_output_func(utils_logfile.log_line) session.set_output_params((log_file,)) - session.set_log_file(os.path.basename(log_file)) + session.set_log_file(log_file) + session.close_hooks += [utils_logfile.close_own_log_file] session.send_ctrl("raw") # Wrap io interfaces. diff --git a/virttest/test_setup/__init__.py b/virttest/test_setup/__init__.py index f4c560f3520..976e65917d8 100644 --- a/virttest/test_setup/__init__.py +++ b/virttest/test_setup/__init__.py @@ -27,6 +27,7 @@ from virttest import data_dir from virttest import error_context from virttest import cpu +from virttest import utils_logfile from virttest import utils_misc from virttest import versionable_class from virttest import openvswitch @@ -1646,7 +1647,7 @@ def sr_iov_setup(self): file_name = "host_dmesg_after_load_%s.txt" % self.driver LOG.info("Log dmesg after loading '%s' to '%s'.", self.driver, file_name) - utils_misc.log_line(file_name, dmesg) + utils_logfile.log_line(file_name, dmesg) self.setup = None return True diff --git a/virttest/utils_logfile.py b/virttest/utils_logfile.py index bd915e8a647..ecdb3dfd539 100644 --- a/virttest/utils_logfile.py +++ b/virttest/utils_logfile.py @@ -1,6 +1,9 @@ """ Control log file utility functions. -An easy way to log lines to files when the logging system can't be used + +An easy way to log lines to files when the logging system can't be used. + +Naive module that keeps tacks of some opened files and somehow manages them. :copyright: 2020 Red Hat Inc. """ @@ -13,7 +16,6 @@ from avocado.core import exceptions from avocado.utils import aurl from avocado.utils import path as utils_path -from aexpect.utils.genio import _open_log_files from avocado.utils.astring import string_safe_encode from virttest import data_dir @@ -23,6 +25,9 @@ _log_file_dir = data_dir.get_tmp_dir() _log_lock = threading.RLock() +# File descriptor dictionary for all open log files +_open_log_files = {} # pylint: disable=C0103 + def _acquire_lock(lock, timeout=10): """ @@ -139,7 +144,7 @@ def get_log_filename(filename): def close_log_file(filename): """ - Close the log file + Close all files that use the same base name as filename. :param filename: Log file name :raise: LogLockError if the lock is unavailable @@ -150,13 +155,18 @@ def close_log_file(filename): raise LogLockError("Could not acquire exclusive lock to access" " _open_log_files") try: - for k in _open_log_files: - if os.path.basename(k) == filename: - f = _open_log_files[k] - f.close() - remove.append(k) + for log_file, log_fd in _open_log_files.items(): + if os.path.basename(log_file) == os.path.basename(filename): + log_fd.close() + remove.append(log_file) if remove: for key_to_remove in remove: _open_log_files.pop(key_to_remove) + finally: _log_lock.release() + + +def close_own_log_file(self): + """Closing hook for sessions whose log_file attribute should be passed along.""" + close_log_file(self.log_file) diff --git a/virttest/utils_misc.py b/virttest/utils_misc.py index f414c072511..c71fad80736 100644 --- a/virttest/utils_misc.py +++ b/virttest/utils_misc.py @@ -41,8 +41,6 @@ except NameError: basestring = (str, bytes) -from aexpect.utils.genio import _open_log_files - from avocado.core import exceptions from avocado.utils import distro from avocado.utils import git @@ -53,7 +51,6 @@ from avocado.utils import download from avocado.utils import linux_modules from avocado.utils import memory -from avocado.utils.astring import string_safe_encode from avocado.utils.astring import to_text # Symlink avocado implementation of process functions from avocado.utils.process import CmdResult @@ -77,6 +74,7 @@ from virttest import error_context from virttest import utils_selinux from virttest import utils_disk +from virttest import utils_logfile from virttest import logging_manager from virttest import kernel_interface @@ -395,24 +393,7 @@ def get_virt_test_open_fds(): return get_open_fds(os.getpid()) -# An easy way to log lines to files when the logging system can't be used - -_log_file_dir = data_dir.get_tmp_dir() -_log_lock = threading.RLock() - - -def _acquire_lock(lock, timeout=10): - end_time = time.time() + timeout - while time.time() < end_time: - if lock.acquire(False): - return True - time.sleep(0.05) - return False - - -class LogLockError(Exception): - pass - +# Deprecated log functionality def log_line(filename, line): """ @@ -422,32 +403,9 @@ def log_line(filename, line): the dir set by set_log_file_dir(). :param line: Line to write. """ - global _open_log_files, _log_file_dir, _log_lock - - if not _acquire_lock(_log_lock): - raise LogLockError("Could not acquire exclusive lock to access" - " _open_log_files") - log_file = get_log_filename(filename) - base_file = os.path.basename(log_file) - try: - if base_file not in _open_log_files: - # First, let's close the log files opened in old directories - close_log_file(base_file) - # Then, let's open the new file - try: - os.makedirs(os.path.dirname(log_file)) - except OSError: - pass - _open_log_files[base_file] = open(log_file, "w") - timestr = time.strftime("%Y-%m-%d %H:%M:%S") - try: - line = string_safe_encode(line) - except UnicodeDecodeError: - line = line.decode("utf-8", "ignore").encode("utf-8") - _open_log_files[base_file].write("%s: %s\n" % (timestr, line)) - _open_log_files[base_file].flush() - finally: - _log_lock.release() + logging.warning("Calling log functions from `utils_misc` is deprecated, " + "please use `utils_logfile` for the purpose") + utils_logfile.log_line(filename, line) def set_log_file_dir(directory): @@ -456,8 +414,9 @@ def set_log_file_dir(directory): :param dir: Directory for log files. """ - global _log_file_dir - _log_file_dir = directory + logging.warning("Calling log functions from `utils_misc` is deprecated, " + "please use `utils_logfile` for the purpose") + utils_logfile.set_log_file_dir(directory) def get_log_file_dir(): @@ -465,32 +424,22 @@ def get_log_file_dir(): get the base directory for log files created by log_line(). """ - global _log_file_dir - return _log_file_dir + logging.warning("Calling log functions from `utils_misc` is deprecated, " + "please use `utils_logfile` for the purpose") + return utils_logfile.get_log_file_dir() def get_log_filename(filename): """return full path of log file name""" - return get_path(_log_file_dir, filename) + logging.warning("Calling log functions from `utils_misc` is deprecated, " + "please use `utils_logfile` for the purpose") + return utils_logfile.get_log_filename(filename) def close_log_file(filename): - global _open_log_files, _log_file_dir, _log_lock - remove = [] - if not _acquire_lock(_log_lock): - raise LogLockError("Could not acquire exclusive lock to access" - " _open_log_files") - try: - for k in _open_log_files: - if os.path.basename(k) == filename: - f = _open_log_files[k] - f.close() - remove.append(k) - if remove: - for key_to_remove in remove: - _open_log_files.pop(key_to_remove) - finally: - _log_lock.release() + logging.warning("Calling log functions from `utils_misc` is deprecated, " + "please use `utils_logfile` for the purpose") + return utils_logfile.close_log_file() # The following are miscellaneous utility functions. @@ -4363,7 +4312,7 @@ def get_sosreport(path=None, session=None, remote_ip=None, remote_pwd=None, report_name = session.cmd_output(cmd) else: report_name = process.getoutput(cmd) - path = host_path = get_path(get_log_file_dir(), + path = host_path = get_path(utils_logfile.get_log_file_dir(), "sosreport-%s" % report_name) if session: func = session.cmd_status_output diff --git a/virttest/virt_vm.py b/virttest/virt_vm.py index da627754357..4d0de7a2d89 100644 --- a/virttest/virt_vm.py +++ b/virttest/virt_vm.py @@ -17,6 +17,7 @@ import six from six.moves import xrange +from virttest import utils_logfile from virttest import utils_misc from virttest import utils_net from virttest import remote as remote_old @@ -1108,12 +1109,13 @@ def login(self, nic_index=0, timeout=LOGIN_TIMEOUT, log_filename = ("session-%s-%s-%s.log" % (self.name, time.strftime("%m-%d-%H-%M-%S"), utils_misc.generate_random_string(4))) - log_filename = utils_misc.get_log_filename(log_filename) - log_function = utils_misc.log_line + log_filename = utils_logfile.get_log_filename(log_filename) + log_function = utils_logfile.log_line session = remote.remote_login(client, address, port, username, password, prompt, linesep, log_filename, log_function, timeout, interface=neigh_attach_if) + session.close_hooks += [utils_logfile.close_own_log_file] session.set_status_test_command(self.params.get("status_test_command", "")) self.remote_sessions.append(session) @@ -1276,7 +1278,7 @@ def copy_files_to(self, host_path, guest_path, nic_index=0, limit="", log_filename = ("transfer-%s-to-%s-%s.log" % (self.name, address, utils_misc.generate_random_string(4))) - log_function = utils_misc.log_line + log_function = utils_logfile.log_line remote.copy_files_to(address, client, username, @@ -1291,7 +1293,7 @@ def copy_files_to(self, host_path, guest_path, nic_index=0, limit="", timeout=timeout, interface=neigh_attach_if, filesize=filesize) - utils_misc.close_log_file(log_filename) + utils_logfile.close_log_file(log_filename) @error_context.context_aware def copy_files_from(self, guest_path, host_path, nic_index=0, limit="", @@ -1323,7 +1325,7 @@ def copy_files_from(self, guest_path, host_path, nic_index=0, limit="", log_filename = ("transfer-%s-from-%s-%s.log" % (self.name, address, utils_misc.generate_random_string(4))) - log_function = utils_misc.log_line + log_function = utils_logfile.log_line remote.copy_files_from(address, client, username, @@ -1338,7 +1340,7 @@ def copy_files_from(self, guest_path, host_path, nic_index=0, limit="", timeout=timeout, interface=neigh_attach_if, filesize=filesize) - utils_misc.close_log_file(log_filename) + utils_logfile.close_log_file(log_filename) def _create_serial_console(self): """ From 4d852bbef4fba5559197a79807a1fd66fc878050 Mon Sep 17 00:00:00 2001 From: nanliu Date: Thu, 23 Nov 2023 16:42:17 +0800 Subject: [PATCH 02/15] cpu: Add missed cpu model flags This CPU model also require the three cpu flags to boot up forcibly. Signed-off-by: nanliu --- virttest/cpu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virttest/cpu.py b/virttest/cpu.py index 248c8827dfb..bfdfde7030e 100644 --- a/virttest/cpu.py +++ b/virttest/cpu.py @@ -73,7 +73,7 @@ "Opteron_G1": "", "SapphireRapids": ("serialize,tsxldtrk|tsx-ldtrk,amx_bf16," "amx_tile,amx_int8,avx512_bf16,avx_vnni," - "avx512_fp16"), + "avx512_fp16,hle,rtm,taa-no"), "Snowridge": "split_lock_detect,gfni,movdiri,movdiri64b,cldemote", "Cooperlake": "avx512_bf16,stibp,arch_capabilities,hle,rtm", "Icelake-Server": "avx512_vnni,la57,clflushopt,hle,rtm", From 68c0b392c2d47ecc2f971fdc0a33016f2b79db9f Mon Sep 17 00:00:00 2001 From: Plamen Dimitrov Date: Fri, 17 Nov 2023 14:09:31 +0200 Subject: [PATCH 03/15] Drop usage of placeholder session log file attribute Since we are handling the complete log file setup and cleanup on the Avocado VT side we can use the more powerful close hook approach to provide the log file path via currying and a function wrapper. Signed-off-by: Plamen Dimitrov --- virttest/libvirt_vm.py | 3 +-- virttest/qemu_vm.py | 3 +-- virttest/remote.py | 3 +-- virttest/utils_logfile.py | 10 +++++++--- virttest/utils_misc.py | 2 +- virttest/virt_vm.py | 4 ++-- 6 files changed, 13 insertions(+), 12 deletions(-) diff --git a/virttest/libvirt_vm.py b/virttest/libvirt_vm.py index 9284654ab90..a62e83958f4 100644 --- a/virttest/libvirt_vm.py +++ b/virttest/libvirt_vm.py @@ -1472,8 +1472,7 @@ def _create_serial_console(self): self.serial_console_log = os.path.join(utils_logfile.get_log_file_dir(), output_filename) # Cause serial_console.close() to close open log file - self.serial_console.set_log_file(self.serial_console_log) - self.serial_console.close_hooks += [utils_logfile.close_own_log_file] + self.serial_console.close_hooks += [utils_logfile.close_own_log_file(self.serial_console_log)] def set_root_serial_console(self, device, remove=False): """ diff --git a/virttest/qemu_vm.py b/virttest/qemu_vm.py index 2ef256721ba..a354e3df87e 100644 --- a/virttest/qemu_vm.py +++ b/virttest/qemu_vm.py @@ -3534,8 +3534,7 @@ def create(self, name=None, params=None, root_dir=None, auto_close=False, output_func=utils_logfile.log_line, output_params=(outfile,)) - self.logsessions[key].set_log_file(outfile) - self.logsessions[key].close_hooks += [utils_logfile.close_own_log_file] + self.logsessions[key].close_hooks += [utils_logfile.close_own_log_file(outfile)] # Wait for IO channels setting up completely, # such as serial console. diff --git a/virttest/remote.py b/virttest/remote.py index 9e4eabfbf66..b442e463f55 100644 --- a/virttest/remote.py +++ b/virttest/remote.py @@ -86,8 +86,7 @@ def remote_commander(client, host, port, username, password, prompt, log_file = utils_logfile.get_log_filename(log_filename) session.set_output_func(utils_logfile.log_line) session.set_output_params((log_file,)) - session.set_log_file(log_file) - session.close_hooks += [utils_logfile.close_own_log_file] + session.close_hooks += [utils_logfile.close_own_log_file(log_file)] session.send_ctrl("raw") # Wrap io interfaces. diff --git a/virttest/utils_logfile.py b/virttest/utils_logfile.py index ecdb3dfd539..e5c08d8311d 100644 --- a/virttest/utils_logfile.py +++ b/virttest/utils_logfile.py @@ -167,6 +167,10 @@ def close_log_file(filename): _log_lock.release() -def close_own_log_file(self): - """Closing hook for sessions whose log_file attribute should be passed along.""" - close_log_file(self.log_file) +def close_own_log_file(log_file): + """Closing hook for sessions with log_file managed locally.""" + + def hook(self): + close_log_file(log_file) + + return hook diff --git a/virttest/utils_misc.py b/virttest/utils_misc.py index c71fad80736..eff7bcc910c 100644 --- a/virttest/utils_misc.py +++ b/virttest/utils_misc.py @@ -439,7 +439,7 @@ def get_log_filename(filename): def close_log_file(filename): logging.warning("Calling log functions from `utils_misc` is deprecated, " "please use `utils_logfile` for the purpose") - return utils_logfile.close_log_file() + return utils_logfile.close_log_file(filename) # The following are miscellaneous utility functions. diff --git a/virttest/virt_vm.py b/virttest/virt_vm.py index 4d0de7a2d89..38c5b238b41 100644 --- a/virttest/virt_vm.py +++ b/virttest/virt_vm.py @@ -1114,8 +1114,8 @@ def login(self, nic_index=0, timeout=LOGIN_TIMEOUT, session = remote.remote_login(client, address, port, username, password, prompt, linesep, log_filename, log_function, - timeout, interface=neigh_attach_if) - session.close_hooks += [utils_logfile.close_own_log_file] + timeout, neigh_attach_if) + session.close_hooks += [utils_logfile.close_own_log_file(log_filename)] session.set_status_test_command(self.params.get("status_test_command", "")) self.remote_sessions.append(session) From 6f36f8c10916ec043414c1acaa9b0a9efcb9d109 Mon Sep 17 00:00:00 2001 From: Plamen Dimitrov Date: Thu, 27 Oct 2022 09:45:31 +0300 Subject: [PATCH 04/15] Configure vm.destroy gracefulness for performance when discarding vms During environment setup we discard vms that are not going to participate in a test but doing so gracefully results in a large timeout (60 seconds by default) for each one and slows down the test and overall test run significantly. As for some plugins the discarded vm will be fully restored from a clean state when it is needed again by a test, graceful shutdown is not always needed and ridding ourselves of it will improve performance for free in these cases. All default cases can still not do this to keep their reused images clean. Signed-off-by: Plamen Dimitrov --- virttest/env_process.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/virttest/env_process.py b/virttest/env_process.py index 156bb37602d..921253098cb 100644 --- a/virttest/env_process.py +++ b/virttest/env_process.py @@ -1156,6 +1156,7 @@ def preprocess(test, params, env): # leave them untouched if they have to be disregarded only for this test requested_vms = params.objects("vms") keep_unrequested_vms = params.get_boolean("keep_unrequested_vms", False) + kill_unrequested_vms_gracefully = params.get_boolean("kill_unrequested_vms_gracefully", True) for key in list(env.keys()): vm = env[key] if not isinstance(vm, virt_vm.BaseVM): @@ -1165,7 +1166,7 @@ def preprocess(test, params, env): LOG.debug("The vm %s is registered in the env and disregarded " "in the current test", vm.name) else: - vm.destroy() + vm.destroy(gracefully=kill_unrequested_vms_gracefully) del env[key] global KVM_MODULE_HANDLERS From d0de7208b201dcd134fe7cbabedf6d826bb5f3c8 Mon Sep 17 00:00:00 2001 From: Dan Zheng Date: Fri, 1 Sep 2023 18:35:13 +0800 Subject: [PATCH 05/15] utils_memory: support to seach new meminfo file Sometimes we need to search specific node meminfo file instead of /proc/meminfo Signed-off-by: Dan Zheng --- virttest/staging/utils_memory.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/virttest/staging/utils_memory.py b/virttest/staging/utils_memory.py index 6b0a12b4e20..900529009f2 100644 --- a/virttest/staging/utils_memory.py +++ b/virttest/staging/utils_memory.py @@ -13,28 +13,34 @@ # Returns total memory in kb -def read_from_meminfo(key, session=None): +def read_from_meminfo(key, session=None, node_id=''): """ wrapper to return value from /proc/meminfo using key :param key: filter based on the key :param session: ShellSession Object of remote host / guest + :param node_id: str, numa node id :return: value mapped to the key of type int """ func = process.getoutput if session: func = session.cmd_output - meminfo = func('grep %s /proc/meminfo' % key) - return int(re.search(r'\d+', meminfo).group(0)) + search_file = '/sys/devices/system/node/node%s/meminfo' % node_id \ + if node_id else '/proc/meminfo' + meminfo = func('grep %s %s' % (key, search_file)) + return int(re.findall(r'%s:\s+(\d+)' % key, meminfo)[0]) -def memtotal(session=None): + +def memtotal(session=None, node_id=''): """ - Method to get the memtotal from /proc/meminfo + Method to get the memtotal from /proc/meminfo or + /sys/devices/system/node/nodexx/meminfo :param session: ShellSession Object of remote host / guest + :param node_id: str, numa node id """ - return read_from_meminfo('MemTotal', session=session) + return read_from_meminfo('MemTotal', session=session, node_id=node_id) def freememtotal(session=None): From 3b23d2e683bf035f0e5edcb7342f53fd188fff72 Mon Sep 17 00:00:00 2001 From: Wenli Quan Date: Tue, 21 Nov 2023 17:09:13 +0800 Subject: [PATCH 06/15] Add_pcidevice: format cmd line to json Signed-off-by: Wenli Quan --- virttest/qemu_vm.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/virttest/qemu_vm.py b/virttest/qemu_vm.py index 7f128f58a56..49b279c695f 100644 --- a/virttest/qemu_vm.py +++ b/virttest/qemu_vm.py @@ -936,6 +936,9 @@ def add_pcidevice(devices, host, params, device_driver="pci-assign", pci_bus='pci.0'): if devices.has_device(device_driver): dev = QDevice(device_driver, parent_bus=pci_bus) + set_cmdline_format_by_cfg(dev, + self._get_cmdline_format_cfg(), + 'nics') else: dev = qdevices.QCustomDevice('pcidevice', parent_bus=pci_bus) help_cmd = "%s -device %s,\? 2>&1" % (qemu_binary, device_driver) From 9f5a18b7eca63a18987194f21c2f74a0bef155d9 Mon Sep 17 00:00:00 2001 From: lcheng Date: Wed, 6 Dec 2023 12:17:27 +0800 Subject: [PATCH 07/15] Add a function to check listen address Signed-off-by: lcheng --- virttest/utils_libvirt/libvirt_network.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/virttest/utils_libvirt/libvirt_network.py b/virttest/utils_libvirt/libvirt_network.py index e031392b73b..f621d4a3ee7 100644 --- a/virttest/utils_libvirt/libvirt_network.py +++ b/virttest/utils_libvirt/libvirt_network.py @@ -342,3 +342,14 @@ def change_tcp_config(expected_dict): tcp_retries2, tcp_fin_timeout)) LOG.debug("Change TCP config: %s", cmd) process.run(cmd, shell=True, ignore_status=False) + + +def check_listen_address(params): + """ + Check listen address by ss command + + :param params: dictionary with the test parameter, get listen address + """ + listen_address = params.get("listen_address") + + process.run(f"ss -tnap|grep qemu-kvm| grep '{listen_address}'", shell=True, ignore_status=False) From e400b0c06900c6aec73d8c83eab88ec4fdceb630 Mon Sep 17 00:00:00 2001 From: lcheng Date: Mon, 30 Oct 2023 12:27:11 +0800 Subject: [PATCH 08/15] Update event timeout for copy storage migration It takes longer to get the desired event in copy storage migration. So update event timeout. Signed-off-by: lcheng --- virttest/migration.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/virttest/migration.py b/virttest/migration.py index 7243e470aa1..963c425ad6a 100644 --- a/virttest/migration.py +++ b/virttest/migration.py @@ -412,6 +412,7 @@ def _run_complex_func(vm, one_func, virsh_event_session=None): after_event = one_func.get('after_event') before_event = one_func.get('before_event') func = one_func.get('func') + wait_for_after_event_timeout = one_func.get('wait_for_after_event_timeout', '30') if after_event and not virsh_event_session: raise exceptions.TestError("virsh session for collecting domain " "events is not provided") @@ -421,7 +422,7 @@ def _run_complex_func(vm, one_func, virsh_event_session=None): "%s", virsh_event_session.get_stripped_output()) if not utils_misc.wait_for( lambda: re.findall(after_event, - virsh_event_session.get_stripped_output()), 30): + virsh_event_session.get_stripped_output()), wait_for_after_event_timeout): raise exceptions.TestError("Unable to find " "event {}".format(after_event)) LOG.debug("Receive the event '{}'".format(after_event)) From 928bfa1cb520686e73aabd7ce47b6a99938c6d19 Mon Sep 17 00:00:00 2001 From: lcheng Date: Wed, 6 Dec 2023 12:33:56 +0800 Subject: [PATCH 09/15] Add a function to check item multiple times by blockjob Signed-off-by: lcheng --- virttest/utils_libvirt/libvirt_disk.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/virttest/utils_libvirt/libvirt_disk.py b/virttest/utils_libvirt/libvirt_disk.py index 403bd79c4a2..f55bbeaaded 100644 --- a/virttest/utils_libvirt/libvirt_disk.py +++ b/virttest/utils_libvirt/libvirt_disk.py @@ -614,3 +614,28 @@ def check_virtual_disk_io(vm, partition, path="/dev/%s"): finally: if session: session.close() + + +def check_item_by_blockjob(params): + """ + Check item by blockjob, for example, bandwidth, progress. + + :param params: dictionary with the test parameter + """ + vm_name = params.get("main_vm") + check_item = params.get("check_item") + check_item_value = params.get("check_item_value") + target_disk = params.get("target_disk", "vda") + wait_for_timeout = params.get("wait_for_timeout", "10") + + if check_item == "none": + if not utils_misc.wait_for( + lambda: libvirt.check_blockjob(vm_name, target_disk), + wait_for_timeout): + raise exceptions.TestFail("Check failed.") + else: + if not utils_misc.wait_for( + lambda: libvirt.check_blockjob(vm_name, + target_disk, check_item, check_item_value), + wait_for_timeout): + raise exceptions.TestFail("Check '%s' failed." % check_item) From 77568d99ced3f1eac08b4207ab620b70014a1c41 Mon Sep 17 00:00:00 2001 From: nanli Date: Thu, 23 Nov 2023 15:37:33 +0800 Subject: [PATCH 10/15] add support for address in memory target Signed-off-by: nanli --- virttest/libvirt_xml/devices/memory.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/virttest/libvirt_xml/devices/memory.py b/virttest/libvirt_xml/devices/memory.py index ccb7b913e3c..de762612442 100644 --- a/virttest/libvirt_xml/devices/memory.py +++ b/virttest/libvirt_xml/devices/memory.py @@ -65,7 +65,8 @@ class Target(base.base.LibvirtXMLBase): __slots__ = ('size', 'size_unit', 'node', 'label', 'requested_size', 'requested_unit', 'current_size', 'current_unit', - 'block_size', 'block_unit', 'readonly') + 'block_size', 'block_unit', 'readonly', + 'address') def __init__(self, virsh_instance=base.base.virsh): accessors.XMLElementInt('size', @@ -113,6 +114,11 @@ def __init__(self, virsh_instance=base.base.virsh): 'virsh_instance': virsh_instance}) accessors.XMLElementBool('readonly', self, parent_xpath='/', tag_name='readonly') + accessors.XMLElementNest('address', self, parent_xpath='/', + tag_name='address', subclass=Memory.Address, + subclass_dargs={ + 'type_name': 'pci', + 'virsh_instance': virsh_instance}) super(self.__class__, self).__init__(virsh_instance=virsh_instance) self.xml = '' From 04a89c012e040ba8b8ee750f4a69b09a61c8da67 Mon Sep 17 00:00:00 2001 From: Plamen Dimitrov Date: Sun, 3 Dec 2023 14:55:19 +0800 Subject: [PATCH 11/15] Allow for both filtered and unfiltered log file closing The environment setup module could close all log files once all sessions are closed but we have to keep backwards compatibility with other uses cases of closing just a single or few log files. Signed-off-by: Plamen Dimitrov --- virttest/env_process.py | 3 +++ virttest/utils_logfile.py | 7 ++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/virttest/env_process.py b/virttest/env_process.py index 698e65a3998..93f527e223c 100644 --- a/virttest/env_process.py +++ b/virttest/env_process.py @@ -45,6 +45,7 @@ from virttest import migration from virttest import utils_kernel_module from virttest import arch +from virttest import utils_logfile from virttest.utils_conn import SSHConnection from virttest.utils_version import VersionInterval from virttest.staging import service @@ -630,6 +631,8 @@ def postprocess_vm(test, params, env, name): except Exception: pass + utils_logfile.close_log_file() + if params.get("vm_extra_dump_paths") is not None: vm_extra_dumps = os.path.join(test.outputdir, "vm_extra_dumps") if not os.path.exists(vm_extra_dumps): diff --git a/virttest/utils_logfile.py b/virttest/utils_logfile.py index e5c08d8311d..bb7ff7fe518 100644 --- a/virttest/utils_logfile.py +++ b/virttest/utils_logfile.py @@ -142,9 +142,9 @@ def get_log_filename(filename): os.path.abspath(utils_path.get_path(_log_file_dir, filename))) -def close_log_file(filename): +def close_log_file(filename="*"): """ - Close all files that use the same base name as filename. + Close log files with the same base name as filename or all by default. :param filename: Log file name :raise: LogLockError if the lock is unavailable @@ -156,7 +156,8 @@ def close_log_file(filename): " _open_log_files") try: for log_file, log_fd in _open_log_files.items(): - if os.path.basename(log_file) == os.path.basename(filename): + if (filename == '*' or + os.path.basename(log_file) == os.path.basename(filename)): log_fd.close() remove.append(log_file) if remove: From 936fb6684914340e3eeb30b416b7157ab2d99b16 Mon Sep 17 00:00:00 2001 From: Miriam Deng Date: Tue, 12 Dec 2023 12:02:33 +0800 Subject: [PATCH 12/15] VM_bootloader:provide bootloader's version for each arch x86_64: seabios and edk2 ppc64, ppc64le: SLOF aarch64: edk2 Signed-off-by: Miriam Deng --- virttest/env_process.py | 11 +++++++++++ virttest/shared/cfg/base.cfg | 7 +++++++ 2 files changed, 18 insertions(+) diff --git a/virttest/env_process.py b/virttest/env_process.py index bd7afb63727..33a121689d0 100644 --- a/virttest/env_process.py +++ b/virttest/env_process.py @@ -1244,6 +1244,17 @@ def preprocess(test, params, env): test.cancel("Got host qemu version:%s, which is not in %s" % (host_qemu, required_qemu)) + # Get the version of bootloader + vm_bootloader_ver_cmd = params.get("vm_bootloader_ver_cmd", "") + if vm_bootloader_ver_cmd: + try: + vm_bootloader_ver = a_process.run( + vm_bootloader_ver_cmd, shell=True).stdout_text.strip() + except a_process.CmdError: + vm_bootloader_ver = "Unkown" + version_info["vm_bootloader_version"] = str(vm_bootloader_ver) + LOG.debug("vm bootloader version: %s", vm_bootloader_ver) + # Get the Libvirt version if vm_type == "libvirt": libvirt_ver_cmd = params.get("libvirt_ver_cmd", "libvirtd -V|awk -F' ' '{print $3}'") diff --git a/virttest/shared/cfg/base.cfg b/virttest/shared/cfg/base.cfg index f4b8832bd74..d98b8bf27cc 100644 --- a/virttest/shared/cfg/base.cfg +++ b/virttest/shared/cfg/base.cfg @@ -1087,3 +1087,10 @@ uuid_dimm = "" # Set image_extent_size_hint, displayed in qemu-img info extend_size_hint field. #image_extent_size_hint = 1G #To set extent_size_hint to 1G + +# +#VM bootloader params +# +#Notes: please make sure the corresponding firmware before you set this value! +#eg. for x86_64 +#vm_bootloader_ver_cmd = "rpm -qa|grep ^sea" From 0f391e9193bd9820e1e7a695cb3f329d71805a75 Mon Sep 17 00:00:00 2001 From: Yalan Zhang Date: Tue, 19 Dec 2023 00:06:12 -0500 Subject: [PATCH 13/15] Fix the get_default_gateway Without the session parameter, utils_misc.cmd_status_output() will always get default gateway from the host. Fix the issue by adding the parameter. Signed-off-by: Yalan Zhang --- virttest/utils_net.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virttest/utils_net.py b/virttest/utils_net.py index 5185c67c245..af573004f4b 100644 --- a/virttest/utils_net.py +++ b/virttest/utils_net.py @@ -3916,7 +3916,7 @@ def get_default_gateway(iface_name=False, session=None, ip_ver='ipv4', else: cmd = "%s | awk '/default/ { print $3 }'" % ip_cmd try: - _, output = utils_misc.cmd_status_output(cmd, shell=True) + _, output = utils_misc.cmd_status_output(cmd, shell=True, session=session) if session: LOG.debug("Guest default gateway is %s", output) else: From 848361eb71185e0ee6940ca22fc737eb9984c88c Mon Sep 17 00:00:00 2001 From: Sebastian Mitterle Date: Thu, 7 Dec 2023 05:56:24 -0500 Subject: [PATCH 14/15] utils_libvirt/libvirt_disk: add function to get disk not on / Use "/" mount information to identify a disk and return its name. Signed-off-by: Sebastian Mitterle --- virttest/utils_libvirt/libvirt_disk.py | 78 ++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/virttest/utils_libvirt/libvirt_disk.py b/virttest/utils_libvirt/libvirt_disk.py index 403bd79c4a2..5ad1f9b5d64 100644 --- a/virttest/utils_libvirt/libvirt_disk.py +++ b/virttest/utils_libvirt/libvirt_disk.py @@ -24,6 +24,84 @@ LOG = logging.getLogger('avocado.' + __name__) +def get_non_root_disk_name(session): + """ + Returns the first disk name under /dev whose device doesn't have any + partitions or volumes or itself mounting "/". + Assuming that the system is installed on a single disk, this + can be used to identify an added disk. + It also includes read-only devices as such, e.g. sr0. + + It iterates over output with the following structure in mind: + vda + ├─vda1 /boot + └─vda2 + ├─rhel-root / + └─rhel-swap [SWAP] + But also considers corner cases where everything is for example on vda and + no swap. + + :param session: If given the command will be executed in this VM or + remote session. + :return (name, mount_point): A tuple containing the device name and the mount + point. If the information doesn't exist it's returned as empty + string. + :raises TestError: If there's no additional disk or no mount on "/" or when + the disk information cannot be retrieved. + """ + cmd = "lsblk -n -o NAME,MOUNTPOINT" + s, o = utils_misc.cmd_status_output(cmd, + ignore_status=False, + shell=True, + verbose=True, + session=session) + if s: + raise exceptions.TestError("Couldn't list block devices: '%s'" % o) + LOG.debug("lsblk output:\n%s", o) + lines = o.split("\n").copy() + root_disk = None + root_mounted = False + disk_pattern = re.compile(r'^([a-z]+|sr\d)[\s\t$]') + entry_pattern = re.compile(r'(.*)[\s\t]+(.*)') + idx = 0 + while -1 < idx < len(lines) + 1: + line = lines[idx] + entry = entry_pattern.match(line) + if not entry: + idx = idx + 1 + continue + name, mpoint = entry.groups() + is_disk = disk_pattern.match(name) + if root_mounted: + if is_disk: + root_disk = line + break + else: + idx = idx - 1 + continue + if mpoint == "/": + root_mounted = True + if is_disk: + root_disk = line + break + else: + # we have found "/" but it's not a disk + # go back to find the disk + idx = idx - 1 + continue + idx = idx + 1 + if not root_disk: + raise exceptions.TestError("'/' not mounted in\n%s" % o) + non_root_disks = [x for x in lines + if x != root_disk and disk_pattern.match(x)] + if not non_root_disks: + raise exceptions.TestError("No non root disks found in\n%s" % o) + else: + LOG.debug("Identified non_root_disks %s in\n%s", non_root_disks, o) + name, mpoint = entry_pattern.match(non_root_disks[0]).groups() + return name.strip(), mpoint.strip() + + def create_disk(disk_type, path=None, size="500M", disk_format="raw", extra='', session=None): """ From 926dfef4133a857303bb38be825a844071f171be Mon Sep 17 00:00:00 2001 From: Wenkang Ji Date: Wed, 25 Oct 2023 10:52:30 +0800 Subject: [PATCH 15/15] netkvm: moving functionality of netkvmco.dll to netkvco.exe Use netkvco.exe to replace the netsh netkvm command. netkvmco.dll is removed from the release and converted to an executable, and netkvmco.exe is added which terminates all the calls that previously were terminated by netshell. Additionally, netkvmco.exe was introduced in the prewhql 242 version. ID: 1449 Signed-off-by: Wenkang Ji wji@redhat.com --- virttest/utils_net.py | 9 ++++--- virttest/utils_windows/virtio_win.py | 36 ++++++++++++++++------------ 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/virttest/utils_net.py b/virttest/utils_net.py index 5db2e6ea3d1..e4ab54b1dfb 100644 --- a/virttest/utils_net.py +++ b/virttest/utils_net.py @@ -4266,10 +4266,11 @@ def set_netkvm_param_value(vm, param, value): """ session = vm.wait_for_serial_login(timeout=360) + netkvmco_path = virtio_win.prepare_netkvmco(vm) try: LOG.info("Set %s to %s" % (param, value)) - cmd = 'netsh netkvm setparam 0 param=%s value=%s' - cmd = cmd % (param, value) + exec_src = 'netsh netkvm' if 'netkvmco.dll' in netkvmco_path else netkvmco_path + cmd = f'{exec_src} setparam 0 param={param} value={value}' status, output = session.cmd_status_output(cmd) if status: err = "Error occured when set %s to value %s. " % (param, value) @@ -4296,9 +4297,11 @@ def get_netkvm_param_value(vm, param): """ session = vm.wait_for_serial_login(timeout=360) + netkvmco_path = virtio_win.prepare_netkvmco(vm) try: LOG.info("Get the value of %s" % param) - cmd = 'netsh netkvm getparam 0 param=%s' % param + exec_src = 'netsh netkvm' if 'netkvmco.dll' in netkvmco_path else netkvmco_path + cmd = f'{exec_src} getparam 0 param={param}' status, output = session.cmd_status_output(cmd) if status: err = "Error occured when get value of %s. " % param diff --git a/virttest/utils_windows/virtio_win.py b/virttest/utils_windows/virtio_win.py index 59939082951..f034ca91467 100644 --- a/virttest/utils_windows/virtio_win.py +++ b/virttest/utils_windows/virtio_win.py @@ -112,10 +112,10 @@ def drive_letter_vfd(session): def _get_netkvmco_path(session): """ - Get the proper netkvmco.dll path from iso. + Get the proper netkvmco path from iso. :param session: a session to send cmd - :return: the proper netkvmco.dll path. + :return: the proper netkvmco binary path. """ viowin_ltr = drive_letter_iso(session) @@ -132,26 +132,32 @@ def _get_netkvmco_path(session): raise exceptions.TestError(err) middle_path = "%s\\%s" % (guest_name, guest_arch) - find_cmd = 'dir /b /s %s\\netkvmco.dll | findstr "\\%s\\\\"' - find_cmd %= (viowin_ltr, middle_path) - netkvmco_path = session.cmd(find_cmd).strip() - LOG.info("Found netkvmco.dll file at %s" % netkvmco_path) - return netkvmco_path - + for file_name in ["netkvmco.dll", "netkvmco.exe"]: + find_cmd = 'dir /b /s "%s" | findstr "%s" | findstr "%s"' \ + % (viowin_ltr, middle_path, file_name) + status, output = session.cmd_status_output(find_cmd) + if status != 0: + continue + netkvmco_path = output.strip().split("\n")[0] + LOG.info("Found %s file at %s" % (file_type, netkvmco_path)) + return netkvmco_path def prepare_netkvmco(vm): """ - Copy the proper netkvmco.dll to driver c:\\, and register it. + Prepare the environment to run netkvmco param vm: the target vm """ - LOG.info("Prepare the netkvmco.dll") + LOG.info("Prepare the environment to run netkvmco") session = vm.wait_for_login(timeout=360) try: - netkvmco_path = _get_netkvmco_path(session) - prepare_netkvmco_cmd = "xcopy %s c:\\ /y && " - prepare_netkvmco_cmd += "rundll32 netkvmco.dll," - prepare_netkvmco_cmd += "RegisterNetKVMNetShHelper" - session.cmd(prepare_netkvmco_cmd % netkvmco_path, timeout=240) + get_netkvmco_path = _get_netkvmco_path(session) + if 'netkvmco.dll' in get_netkvmco_path: + prepare_netkvmco_cmd = "xcopy %s c:\\ /y && " + prepare_netkvmco_cmd += "rundll32 netkvmco.dll," + prepare_netkvmco_cmd += "RegisterNetKVMNetShHelper" + session.cmd(prepare_netkvmco_cmd \ + % get_netkvmco_path, timeout=240) finally: session.close() + return get_netkvmco_path