Skip to content

Commit 7292909

Browse files
committed
Refactor file transfer and disk free space utilities
Move run_file_transfer and run_virtio_serial_file_transfer from utils_test/__init__.py into new utils_test/file_transfer.py module. Re-exports in __init__.py preserve backward compatibility. Convert utils_disk.py to utils_disk/ package. Move get_free_disk from utils_misc.py to utils_disk/free_space.py and add new check_free_disk helper. Re-exports in utils_disk/__init__.py and utils_misc.py preserve backward compatibility. Add guest disk free space validation in run_file_transfer to fail early with a clear error when the VM has insufficient space for the transfer. Assisted-by: Claude AI (cursor) human design Signed-off-by: hholoubk <hholoubk@redhat.com> Made-with: Cursor
1 parent a415ed5 commit 7292909

5 files changed

Lines changed: 334 additions & 264 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1754,3 +1754,7 @@ def close(self):
17541754
"""
17551755
if self.g:
17561756
self.g.close()
1757+
1758+
1759+
from virttest.utils_disk.free_space import check_free_disk # noqa: E402,F401
1760+
from virttest.utils_disk.free_space import get_free_disk # noqa: E402,F401

virttest/utils_disk/free_space.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""Guest disk free space utilities."""
2+
3+
import re
4+
5+
from avocado.core import exceptions
6+
7+
from virttest.utils_numeric import normalize_data_size
8+
9+
10+
def get_free_disk(session, mount):
11+
"""Get FreeSpace for given mount point.
12+
13+
:param session: shell Object.
14+
:type session: aexpect.ShellSession
15+
:param mount: mount point(eg. C:, /mnt)
16+
:type mount: str
17+
:return: freespace in M-bytes
18+
:rtype: int
19+
"""
20+
if re.match(r"[a-zA-Z]:", mount):
21+
cmd = f"wmic logicaldisk where \"DeviceID='{mount}'\" "
22+
cmd += "get FreeSpace"
23+
output = session.cmd_output(cmd)
24+
digits = re.findall(r"\d+", output)[0]
25+
free = f"{digits}K"
26+
else:
27+
cmd = f"df -h {mount}"
28+
output = session.cmd_output(cmd)
29+
free = re.findall(r"\b([\d.]+[BKMGPETZ])\b", output, re.M | re.I)[2]
30+
free = float(normalize_data_size(free, order_magnitude="M"))
31+
return int(free)
32+
33+
34+
def check_free_disk(session, mount, required_mb):
35+
"""Check that a guest mount point has enough free space.
36+
37+
:param session: Guest shell session object.
38+
:type session: aexpect.ShellSession
39+
:param mount: Mount point or drive letter (e.g. "/var/tmp", "C:").
40+
:type mount: str
41+
:param required_mb: Minimum required free space in MB.
42+
:type required_mb: int
43+
:raises exceptions.TestError: When free space is below required_mb.
44+
"""
45+
free_mb = get_free_disk(session, mount)
46+
if free_mb < required_mb:
47+
raise exceptions.TestError(
48+
f"Not enough space on guest '{mount}': {free_mb}MB free, "
49+
f"{required_mb}MB required"
50+
)

virttest/utils_misc.py

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2343,26 +2343,7 @@ def __get_unit_index(M):
23432343
return ("%.20f" % data_size).rstrip("0")
23442344

23452345

2346-
def get_free_disk(session, mount):
2347-
"""
2348-
Get FreeSpace for given mount point.
2349-
2350-
:param session: shell Object.
2351-
:param mount: mount point(eg. C:, /mnt)
2352-
2353-
:return string: freespace M-bytes
2354-
"""
2355-
if re.match(r"[a-zA-Z]:", mount):
2356-
cmd = "wmic logicaldisk where \"DeviceID='%s'\" " % mount
2357-
cmd += "get FreeSpace"
2358-
output = session.cmd_output(cmd)
2359-
free = "%sK" % re.findall(r"\d+", output)[0]
2360-
else:
2361-
cmd = "df -h %s" % mount
2362-
output = session.cmd_output(cmd)
2363-
free = re.findall(r"\b([\d.]+[BKMGPETZ])\b", output, re.M | re.I)[2]
2364-
free = float(normalize_data_size(free, order_magnitude="M"))
2365-
return int(free)
2346+
from virttest.utils_disk.free_space import get_free_disk # noqa: F811,E402,F401
23662347

23672348

23682349
def get_free_mem(session, os_type):

virttest/utils_test/__init__.py

Lines changed: 4 additions & 244 deletions
Original file line numberDiff line numberDiff line change
@@ -750,250 +750,10 @@ def run_image_copy(test, params, env):
750750
run_virt_sub_test(test, params, env, params.get("sub_type"))
751751

752752

753-
@error_context.context_aware
754-
def run_file_transfer(test, params, env):
755-
"""
756-
Transfer a file back and forth between host and guest.
757-
758-
1) Boot up a VM.
759-
2) Create a large file by dd on host.
760-
3) Copy this file from host to guest.
761-
4) Copy this file from guest to host.
762-
5) Check if file transfers ended good.
763-
764-
:param test: QEMU test object.
765-
:param params: Dictionary with the test parameters.
766-
:param env: Dictionary with test environment.
767-
"""
768-
vm = env.get_vm(params["main_vm"])
769-
vm.verify_alive()
770-
login_timeout = int(params.get("login_timeout", 360))
771-
session = vm.wait_for_login(timeout=login_timeout)
772-
error_context.context("Login to guest", LOG.info)
773-
transfer_timeout = int(params.get("transfer_timeout", 1000))
774-
clean_cmd = params.get("clean_cmd", "rm -f")
775-
filesize = int(params.get("filesize", 4000))
776-
count = int(filesize / 10) or 1
777-
host_path = tempfile.mktemp(prefix="tmp-", dir=data_dir.get_tmp_dir())
778-
if params.get("os_type") != "windows":
779-
tmp_dir = params.get("tmp_dir", "/var/tmp")
780-
guest_path = tempfile.mktemp(prefix="transferred-", dir=tmp_dir)
781-
else:
782-
tmp_dir = params.get("tmp_dir", "c:\\")
783-
guest_path = "\\".join([tmp_dir, utils_misc.generate_random_string(8)])
784-
guest_path = "\\".join(filter(None, re.split(r"\\+", guest_path)))
785-
cmd = "dd if=/dev/zero of=%s bs=10M count=%d" % (host_path, count)
786-
try:
787-
error_context.context("Creating %dMB file on host" % filesize, LOG.info)
788-
process.run(cmd)
789-
original_md5 = crypto.hash_file(host_path, algorithm="md5")
790-
error_context.context(
791-
"Transferring file host -> guest, " "timeout: %ss" % transfer_timeout,
792-
LOG.info,
793-
)
794-
vm.copy_files_to(
795-
host_path, guest_path, timeout=transfer_timeout, filesize=filesize
796-
)
797-
798-
error_context.context(
799-
"Transferring file guest -> host, " "timeout: %ss" % transfer_timeout,
800-
LOG.info,
801-
)
802-
vm.copy_files_from(
803-
guest_path, host_path, timeout=transfer_timeout, filesize=filesize
804-
)
805-
current_md5 = crypto.hash_file(host_path, algorithm="md5")
806-
807-
error_context.context(
808-
"Compare md5sum between original file and " "transferred file", LOG.info
809-
)
810-
if original_md5 != current_md5:
811-
raise exceptions.TestFail(
812-
"File changed after transfer host -> guest " "and guest -> host"
813-
)
814-
finally:
815-
try:
816-
os.remove(host_path)
817-
except OSError as detail:
818-
LOG.warning("Could not remove temp files in host: '%s'", detail)
819-
LOG.info("Cleaning temp file on guest")
820-
try:
821-
session.cmd("%s %s" % (clean_cmd, guest_path))
822-
except aexpect.ShellError as detail:
823-
LOG.warning("Could not remove temp files in guest: '%s'", detail)
824-
finally:
825-
session.close()
826-
827-
828-
@error_context.context_aware
829-
def run_virtio_serial_file_transfer(
830-
test, params, env, port_name=None, sender="guest", md5_check=True
831-
):
832-
"""
833-
Transfer file between host and guest through virtio serial.
834-
835-
:param test: QEMU test object.
836-
:param params: Dictionary with the test parameters.
837-
:param env: Dictionary with test environment.
838-
:param port_name: VM's serial port name used to transfer data.
839-
:param sender: Who is data sender. guest, host or both.
840-
:param md5_check: Check md5 or not.
841-
"""
842-
843-
def get_virtio_port_host_file(vm, port_name):
844-
"""
845-
Returns separated virtserialports
846-
:param vm: VM object
847-
:return: All virtserialports
848-
"""
849-
for port in vm.virtio_ports:
850-
if isinstance(port, qemu_virtio_port.VirtioSerial):
851-
if port.name == port_name:
852-
return port.hostfile
853-
854-
def run_host_cmd(host_cmd, timeout=720):
855-
return process.run(host_cmd, shell=True, timeout=timeout).stdout_text
856-
857-
def transfer_data(session, host_cmd, guest_cmd, n_time, timeout, md5_check, action):
858-
for num in xrange(n_time):
859-
md5_host = "1"
860-
md5_guest = "2"
861-
LOG.info("Data transfer repeat %s/%s." % (num + 1, n_time))
862-
try:
863-
args = (host_cmd, timeout)
864-
host_thread = utils_misc.InterruptedThread(run_host_cmd, args)
865-
host_thread.start()
866-
g_output = session.cmd_output(guest_cmd, timeout=timeout)
867-
if action == "both":
868-
if "Md5MissMatch" in g_output:
869-
err = "Data lost during file transfer. Md5 miss match."
870-
err += " Script output:\n%s" % g_output
871-
if md5_check:
872-
raise exceptions.TestFail(err)
873-
else:
874-
LOG.warning(err)
875-
else:
876-
md5_re = "md5_sum = (\w{32})"
877-
try:
878-
md5_guest = re.findall(md5_re, g_output)[0]
879-
except Exception:
880-
err = "Fail to get md5, script may fail."
881-
err += " Script output:\n%s" % g_output
882-
raise exceptions.TestError(err)
883-
finally:
884-
if host_thread:
885-
output = ""
886-
output = host_thread.join(10)
887-
if action == "both":
888-
if "Md5MissMatch" in output:
889-
err = "Data lost during file transfer. Md5 miss "
890-
err += "match. Script output:\n%s" % output
891-
if md5_check:
892-
raise exceptions.TestFail(err)
893-
else:
894-
LOG.warning(err)
895-
else:
896-
md5_re = "md5_sum = (\w{32})"
897-
try:
898-
md5_host = re.findall(md5_re, output)[0]
899-
except Exception:
900-
err = "Fail to get md5, script may fail."
901-
err += " Script output:\n%s" % output
902-
raise exceptions.TestError(err)
903-
if action != "both" and md5_host != md5_guest:
904-
err = "Data lost during file transfer. Md5 miss match."
905-
err += " Guest script output:\n %s" % g_output
906-
err += " Host script output:\n%s" % output
907-
if md5_check:
908-
raise exceptions.TestFail(err)
909-
else:
910-
LOG.warning(err)
911-
912-
env["serial_file_transfer_start"] = False
913-
vm = env.get_vm(params["main_vm"])
914-
vm.verify_alive()
915-
timeout = int(params.get("login_timeout", 360))
916-
session = vm.wait_for_login(timeout=timeout)
917-
918-
if not port_name:
919-
port_name = params["file_transfer_serial_port"]
920-
guest_scripts = params["guest_scripts"]
921-
guest_path = params.get("guest_script_folder", "C:\\")
922-
error_context.context("Copy test scripts to guest.", LOG.info)
923-
for script in guest_scripts.split(";"):
924-
link = os.path.join(data_dir.get_root_dir(), "shared", "deps", "serial", script)
925-
vm.copy_files_to(link, guest_path, timeout=60)
926-
host_device = get_virtio_port_host_file(vm, port_name)
927-
928-
dir_name = data_dir.get_tmp_dir()
929-
transfer_timeout = int(params.get("transfer_timeout", 720))
930-
tmp_dir = params.get("tmp_dir", "/var/tmp/")
931-
filesize = int(params.get("filesize", 10))
932-
count = int(filesize)
933-
934-
host_data_file = os.path.join(
935-
dir_name, "tmp-%s" % utils_misc.generate_random_string(8)
936-
)
937-
guest_data_file = os.path.join(
938-
tmp_dir, "tmp-%s" % utils_misc.generate_random_string(8)
939-
)
940-
941-
if sender == "host" or sender == "both":
942-
cmd = "dd if=/dev/zero of=%s bs=1M count=%d" % (host_data_file, count)
943-
error_context.context("Creating %dMB file on host" % filesize, LOG.info)
944-
process.run(cmd)
945-
else:
946-
guest_file_create_cmd = "dd if=/dev/zero of=%s bs=1M count=%d"
947-
guest_file_create_cmd = params.get(
948-
"guest_file_create_cmd", guest_file_create_cmd
949-
)
950-
cmd = guest_file_create_cmd % (guest_data_file, count)
951-
error_context.context("Creating %dMB file on host" % filesize, LOG.info)
952-
session.cmd(cmd, timeout=600)
953-
954-
if sender == "host":
955-
action = "send"
956-
guest_action = "receive"
957-
txt = "Transfer data from host to guest"
958-
elif sender == "guest":
959-
action = "receive"
960-
guest_action = "send"
961-
txt = "Transfer data from guest to host"
962-
else:
963-
action = "both"
964-
guest_action = "both"
965-
txt = "Transfer data between guest and host"
966-
967-
host_script = params.get("host_script", "serial_host_send_receive.py")
968-
host_script = os.path.join(
969-
data_dir.get_root_dir(), "shared", "deps", "serial", host_script
970-
)
971-
host_cmd = "`command -v python python3 | head -1` %s -s %s -f %s -a %s" % (
972-
host_script,
973-
host_device,
974-
host_data_file,
975-
action,
976-
)
977-
guest_script = params.get("guest_script", "VirtIoChannel_guest_send_receive.py")
978-
guest_script = os.path.join(guest_path, guest_script)
979-
980-
guest_cmd = "`command -v python python3 | head -1` %s -d %s -f %s -a %s" % (
981-
guest_script,
982-
port_name,
983-
guest_data_file,
984-
guest_action,
985-
)
986-
n_time = int(params.get("repeat_times", 1))
987-
txt += " for %s times" % n_time
988-
try:
989-
env["serial_file_transfer_start"] = True
990-
transfer_data(
991-
session, host_cmd, guest_cmd, n_time, transfer_timeout, md5_check, action
992-
)
993-
finally:
994-
env["serial_file_transfer_start"] = False
995-
if session:
996-
session.close()
753+
from virttest.utils_test.file_transfer import ( # noqa: F401
754+
run_file_transfer,
755+
run_virtio_serial_file_transfer,
756+
)
997757

998758

999759
def session_handler(func):

0 commit comments

Comments
 (0)