Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion mfd_kvm/hypervisor.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import typing
import xml.etree.ElementTree as ET
from dataclasses import asdict, dataclass, fields
from functools import cached_property
from pathlib import Path, PurePosixPath
from random import choice
from time import sleep, time
Expand Down Expand Up @@ -148,6 +149,18 @@ def __init__(self, *, connection: "PythonConnection") -> None:
self._conn = connection
self.virt_tool = VirshInterface(connection=connection)

@cached_property
def _rsync_available(self) -> bool:
"""Check once per hypervisor instance whether rsync is available."""
return (
self._conn.execute_command(
"command -v rsync",
shell=True,
expected_return_codes={0, 1},
).return_code
== 0
)

@staticmethod
def get_name_from_ip(ip: IPAddress, prefix: str = "amval") -> str:
"""
Expand Down Expand Up @@ -887,11 +900,14 @@ def clone_vm_hdd_image(
)
if path_to_source_image.is_file():
timeout_counter = TimeoutCounter(timeout)
copy_command = "rsync -aqc" if self._rsync_available else "scp"
# show 5 column of ls, size of file
target_size = self._conn.execute_command(
f"ls {path_to_source_image} -l | awk '{{print $5}}'", shell=True
).stdout
copy_process = self._conn.start_process(f"scp {path_to_source_image} {path_to_destination_image}")
copy_process = self._conn.start_process(
f"{copy_command} {path_to_source_image} {path_to_destination_image}"
)
logger.log(log_levels.MODULE_DEBUG, msg="Checking if cloning is completed..")
while not timeout_counter:
if not copy_process.running:
Expand Down
33 changes: 27 additions & 6 deletions tests/unit/test_mfd_kvm/test_hypervisor.py
Original file line number Diff line number Diff line change
Expand Up @@ -893,19 +893,39 @@ def test_clone_vm_hdd_image_timeout_exceeded(self, hv, mocker):
timeout=timeout,
)

def test_clone_vm_hdd_image_source_file_exists_and_succeeded(self, hv, mocker):
@pytest.mark.parametrize(
("rsync_return_code", "copy_command"),
[(0, "rsync -aqc"), (1, "scp")],
)
def test_clone_vm_hdd_image_source_file_exists_and_succeeded(self, hv, mocker, rsync_return_code, copy_command):
timeout_mocker = mocker.patch("mfd_kvm.hypervisor.TimeoutCounter")
timeout_mocker.return_value.__bool__.return_value = False
process = mocker.create_autospec(RPyCProcess)
process.running = False
hv._conn.start_process.return_value = process
dest_path = Path("/foo/destination")
path_mocker = mocker.patch("mfd_kvm.hypervisor.Path")
path_mocker.isfile.return_value = True
new_name = hv.clone_vm_hdd_image(path_to_source_image=path_mocker, path_to_destination_image=dest_path)
path_mocker.is_file.return_value = True
hv._conn.execute_command.side_effect = [
ConnectionCompletedProcess(args="", return_code=rsync_return_code),
ConnectionCompletedProcess(args="", stdout="100", return_code=0),
ConnectionCompletedProcess(args="", stdout="100", return_code=0),
]
new_names = [
hv.clone_vm_hdd_image(path_to_source_image=path_mocker, path_to_destination_image=dest_path)
for _ in range(2)
]

hv._conn.start_process.assert_called_once_with(f"scp {path_mocker} {dest_path}")
assert new_name, str(dest_path)
assert hv._conn.execute_command.call_args_list == [
mocker.call("command -v rsync", shell=True, expected_return_codes={0, 1}),
mocker.call(f"ls {path_mocker} -l | awk '{{print $5}}'", shell=True),
mocker.call(f"ls {path_mocker} -l | awk '{{print $5}}'", shell=True),
]
assert hv._conn.start_process.call_args_list == [
mocker.call(f"{copy_command} {path_mocker} {dest_path}"),
mocker.call(f"{copy_command} {path_mocker} {dest_path}"),
]
assert new_names == [dest_path, dest_path]

def test_clone_vm_hdd_image_still_cloning(self, hv, mocker, caplog):
caplog.set_level(log_levels.MODULE_DEBUG)
Expand All @@ -917,9 +937,10 @@ def test_clone_vm_hdd_image_still_cloning(self, hv, mocker, caplog):
path_mocker = mocker.patch("mfd_kvm.hypervisor.Path")
dest_path = mocker.patch("mfd_kvm.hypervisor.Path")
mocker.patch("mfd_kvm.hypervisor.sleep")
path_mocker.isfile.return_value = True
path_mocker.is_file.return_value = True
dest_path.exists.return_value = True
hv._conn.execute_command.side_effect = [
ConnectionCompletedProcess(args="", return_code=0),
ConnectionCompletedProcess(args="", stdout="100", return_code=0),
ConnectionCompletedProcess(args="", stdout="10", return_code=0),
]
Expand Down
Loading