-
Notifications
You must be signed in to change notification settings - Fork 211
Add and fix hyperv modules test #3853
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,8 @@ | ||
# Copyright (c) Microsoft Corporation. | ||
# Licensed under the MIT license. | ||
|
||
from typing import List | ||
|
||
from typing import List, Literal | ||
from enum import Enum | ||
from assertpy import assert_that | ||
from semver import VersionInfo | ||
|
||
|
@@ -18,9 +18,21 @@ | |
from lisa.operating_system import BSD, Redhat | ||
from lisa.sut_orchestrator import AZURE, HYPERV, READY | ||
from lisa.sut_orchestrator.azure.platform_ import AzurePlatform | ||
from lisa.tools import KernelConfig, LisDriver, Lsinitrd, Lsmod, Modinfo, Modprobe | ||
from lisa.tools import Cat, KernelConfig, LisDriver, Lsinitrd, Lsmod, Modinfo, Modprobe | ||
from lisa.util import LisaException, SkippedException | ||
|
||
ModulesType = Enum( | ||
"ModulesType", | ||
[ | ||
# Modules which dont have "=y" in the kernel config | ||
# and therefore are not built into the kernel. | ||
"NOT_BUILT_IN", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should be
|
||
# Modules which have "=m" in the kernel config | ||
# and therefore are built as loadable modules. | ||
"LOADABLE", | ||
], | ||
) | ||
|
||
|
||
@TestSuiteMetadata( | ||
area="core", | ||
|
@@ -61,7 +73,7 @@ def verify_lis_modules_version(self, node: Node) -> None: | |
lis_driver = node.tools[LisDriver] | ||
lis_version = lis_driver.get_version() | ||
|
||
hv_modules = self._get_not_built_in_modules(node) | ||
hv_modules = self._get_modules_by_type(node) | ||
for module in hv_modules: | ||
module_version = VersionInfo.parse(modinfo.get_version(module)) | ||
assert_that(module_version).described_as( | ||
|
@@ -154,7 +166,7 @@ def _get_built_in_modules(self, node: Node) -> List[str]: | |
) | ||
def verify_hyperv_modules(self, log: Logger, environment: Environment) -> None: | ||
node = environment.nodes[0] | ||
hv_modules = self._get_not_built_in_modules(node) | ||
hv_modules = self._get_modules_by_type(node) | ||
distro_version = node.os.information.version | ||
if len(hv_modules) == 0: | ||
raise SkippedException( | ||
|
@@ -206,45 +218,129 @@ def verify_hyperv_modules(self, log: Logger, environment: Environment) -> None: | |
), | ||
) | ||
def verify_reload_hyperv_modules(self, log: Logger, node: Node) -> None: | ||
# Constants | ||
module = "hv_netvsc" | ||
loop_count = 100 | ||
|
||
if isinstance(node.os, Redhat): | ||
try: | ||
log.debug("Checking LIS installation before reload.") | ||
node.tools.get(LisDriver) | ||
except Exception: | ||
log.debug("Updating LIS failed. Moving on to attempt reload.") | ||
|
||
if module not in self._get_not_built_in_modules(node): | ||
raise SkippedException( | ||
f"{module} is loaded statically into the " | ||
"kernel and therefore can not be reloaded" | ||
preferred_log_level = 4 | ||
log_level = int( | ||
node.tools[Cat] | ||
.read("/proc/sys/kernel/printk", force_run=True, sudo=True) | ||
.split()[0] | ||
) | ||
|
||
# The 10-minute timeout specified in node.execute is not being honoured, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you asking if the log floods cause the test to run longer, specifically up to 50 seconds? How many lines are printed when running this test case? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Taking hv_netvsc as an example where we are trying to run the following command (which runs for 100 iterations as you can see below): I tried running locally to the VMs without LISA: With log level 7: Coming back to LISA: Basically, in the wait_result() method of process.py file, there is a while loop condition: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand the reduced log level. Regarding the timeout, I need more information. Do you mean it happens only in LocalProcess, not SshProcess? If self.is_running() returns False, it means the process has exited. This is by design. How do you know the process is actually running? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you mean it happens only in LocalProcess, not SshProcess?
do you know the process is actually running?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you check if the issue still exists, after moved to modprobe? And please share me the full log, I can take a look. |
||
# as the local process in the spur library stops running after approximately | ||
# 50 seconds. To mitigate this, we set the log level to 4 for any VM where | ||
# the log level exceeds 4. This helps reduce excessive logging during module | ||
# reloads, which could otherwise cause command execution to time out due to | ||
# the loop count being set to 100. | ||
if log_level > preferred_log_level: | ||
log.debug( | ||
f"Current dmesg log level is {log_level}, " | ||
f"setting it to {preferred_log_level} for module reload." | ||
) | ||
self._set_dmesg_log_level(log, node, preferred_log_level) | ||
|
||
result = node.execute( | ||
("for i in $(seq 1 %i); do " % loop_count) | ||
+ f"modprobe -r -v {module}; modprobe -v {module}; " | ||
"done; sleep 1; " | ||
"ip link set eth0 down; ip link set eth0 up; dhclient eth0", | ||
sudo=True, | ||
shell=True, | ||
skipped_modules = [] | ||
failed_modules = {} | ||
hv_modules = [ | ||
"hv_vmbus", | ||
"hv_netvsc", | ||
"hv_storvsc", | ||
"hv_utils", | ||
"hv_balloon", | ||
"hid_hyperv", | ||
"hyperv_keyboard", | ||
"hyperv_fb", | ||
] | ||
loadable_modules = set( | ||
self._get_modules_by_type(node, module_type=ModulesType.LOADABLE) | ||
) | ||
# Constants | ||
try: | ||
for module in hv_modules: | ||
# try: | ||
if module not in loadable_modules: | ||
log.debug(f"{module} is not a reloadable module") | ||
skipped_modules.append(module) | ||
continue | ||
loop_count = 100 | ||
log.debug(f"Reloading {module} for {loop_count} times") | ||
modprobe = node.tools[Modprobe] | ||
|
||
if "is in use" in result.stdout: | ||
raise SkippedException( | ||
f"Module {module} is in use so it cannot be reloaded" | ||
) | ||
result = modprobe.reload( | ||
mod_names=[module], | ||
times=loop_count, | ||
verbose=True, | ||
timeout=600, | ||
nohup=False, | ||
) | ||
|
||
assert_that(result.stdout.count("rmmod")).described_as( | ||
f"Expected {module} to be removed {loop_count} times" | ||
).is_equal_to(loop_count) | ||
assert_that(result.stdout.count("insmod")).described_as( | ||
f"Expected {module} to be inserted {loop_count} times" | ||
).is_equal_to(loop_count) | ||
if ( | ||
"is in use" in result.stdout | ||
or "Device or resource busy" in result.stdout | ||
): | ||
# If the module is in use, it cannot be reloaded. | ||
log.debug(f"Module {module} is in use so it cannot be reloaded") | ||
skipped_modules.append(module) | ||
continue | ||
|
||
if ( | ||
result.stdout.count("rmmod") != loop_count | ||
or result.stdout.count("insmod") != loop_count | ||
): | ||
failure_message = ( | ||
f"Module {module} was not reloaded {loop_count} times. " | ||
f"rmmod count: {result.stdout.count('rmmod')}, " | ||
f"insmod count: {result.stdout.count('insmod')}" | ||
) | ||
failed_modules[module] = failure_message | ||
|
||
finally: | ||
if log_level > preferred_log_level: | ||
log.debug("Restoring dmesg log level to original value") | ||
self._set_dmesg_log_level(log, node, log_level) | ||
if failed_modules: | ||
raise AssertionError( | ||
"The following modules have reload count mismatch:\n" | ||
+ ",\n".join( | ||
f"{module}: {msg}" for module, msg in failed_modules.items() | ||
) | ||
) | ||
|
||
if skipped_modules: | ||
raise SkippedException( | ||
f"The following modules were skipped during" | ||
f" reload: {', '.join(skipped_modules)}. " | ||
"This may be due to them being built into the kernel or in use." | ||
) | ||
|
||
def _set_dmesg_log_level( | ||
squirrelsc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
self, log: Logger, node: Node, preferred_log_level: int | ||
) -> None: | ||
log.debug(f"Setting dmesg log level to {preferred_log_level}") | ||
node.execute(f"dmesg -n {preferred_log_level}", sudo=True, shell=True) | ||
|
||
def _get_not_built_in_modules(self, node: Node) -> List[str]: | ||
new_log_level = int( | ||
node.tools[Cat] | ||
.read("/proc/sys/kernel/printk", force_run=True, sudo=True) | ||
.split()[0] | ||
) | ||
|
||
assert_that(new_log_level).described_as( | ||
f"Expected dmesg log level to be set to {preferred_log_level}, " | ||
f"but it is {new_log_level}." | ||
).is_equal_to(preferred_log_level) | ||
|
||
def _get_modules_by_type( | ||
self, | ||
node: Node, | ||
module_type: ModulesType = ModulesType.NOT_BUILT_IN, | ||
) -> List[str]: | ||
""" | ||
Returns the hv_modules that are not directly loaded into the kernel and | ||
therefore would be expected to show up in lsmod. | ||
|
@@ -264,12 +360,19 @@ def _get_not_built_in_modules(self, node: Node) -> List[str]: | |
"hid_hyperv": "CONFIG_HID_HYPERV_MOUSE", | ||
"hv_balloon": "CONFIG_HYPERV_BALLOON", | ||
"hyperv_keyboard": "CONFIG_HYPERV_KEYBOARD", | ||
"hyperv_fb": "CONFIG_FB_HYPERV", | ||
} | ||
modules = [] | ||
for module in hv_modules_configuration: | ||
if not node.tools[KernelConfig].is_built_in( | ||
hv_modules_configuration[module] | ||
): | ||
modules.append(module) | ||
if module_type == ModulesType.LOADABLE: | ||
if node.tools[KernelConfig].is_built_as_module( | ||
hv_modules_configuration[module] | ||
): | ||
modules.append(module) | ||
elif module_type == ModulesType.NOT_BUILT_IN: | ||
if not node.tools[KernelConfig].is_built_in( | ||
hv_modules_configuration[module] | ||
): | ||
modules.append(module) | ||
|
||
return modules |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please also reuse it inside of the
dhclient
to reduce duplicate code.