Skip to content

Conversation

@meinaLi
Copy link
Contributor

@meinaLi meinaLi commented Sep 10, 2025

Automate cases:
RHEL-187642 pull mode backup and managed save vm together
RHEL-187641 pull mode backup and save vm together
RHEL-199045 Start an existing backup job after libvirtd restarted
RHEL-199046 kill qemu process between libvirtd stop/start when there is an existing pull-mode backup job

Summary by CodeRabbit

  • Tests
    • Added an automated incremental backup lifecycle test suite covering full and incremental backups across VM operations: save, managedsave, service restart, and forced VM process termination.
    • Added a lifecycle test configuration with NBD transfer settings, checkpoint naming/sequencing, and variant steps for different failure/recovery scenarios.
    • Validates expected-error handling, NBD data pulls, recovery flows, and ensures cleanup of created backup files and checkpoints.

@meinaLi
Copy link
Contributor Author

meinaLi commented Sep 10, 2025

# avocado run --vt-type libvirt --vt-omit-data-loss --vt-machine-type q35 incremental_backup.lifecycle
JOB ID     : 132ac4b46af49caf38ef0f21fe1a22e0b566d81c
JOB LOG    : /var/log/avocado/job-results/job-2025-09-10T04.26-132ac4b/job.log
 (1/4) type_specific.io-github-autotest-libvirt.incremental_backup.lifecycle.save_vm: STARTED
 (1/4) type_specific.io-github-autotest-libvirt.incremental_backup.lifecycle.save_vm: PASS (125.94 s)
 (2/4) type_specific.io-github-autotest-libvirt.incremental_backup.lifecycle.managedsave: STARTED
 (2/4) type_specific.io-github-autotest-libvirt.incremental_backup.lifecycle.managedsave: PASS (129.50 s)
 (3/4) type_specific.io-github-autotest-libvirt.incremental_backup.lifecycle.restart_service: STARTED
 (3/4) type_specific.io-github-autotest-libvirt.incremental_backup.lifecycle.restart_service: PASS (95.16 s)
 (4/4) type_specific.io-github-autotest-libvirt.incremental_backup.lifecycle.kill_qemu_process: STARTED
 (4/4) type_specific.io-github-autotest-libvirt.incremental_backup.lifecycle.kill_qemu_process: PASS (93.14 s)
RESULTS    : PASS 4 | ERROR 0 | FAIL 0 | SKIP 0 | WARN 0 | INTERRUPT 0 | CANCEL 0
JOB HTML   : /var/log/avocado/job-results/job-2025-09-10T04.26-132ac4b/results.html
JOB TIME   : 451.93 s

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (1)
libvirt/tests/src/incremental_backup/incremental_backup_lifecycle.py (1)

214-216: File cleanup could fail silently.

The file cleanup in the finally block doesn't handle potential exceptions, which could mask cleanup issues.

Add error handling for file cleanup:

         for file in backup_file_list:
             if os.path.exists(file):
-                os.remove(file)
+                try:
+                    os.remove(file)
+                    test.log.debug(f"Removed backup file: {file}")
+                except OSError as e:
+                    test.log.warning(f"Failed to remove backup file {file}: {e}")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6183805 and d364035.

📒 Files selected for processing (2)
  • libvirt/tests/cfg/incremental_backup/incremental_backup_lifecycle.cfg (1 hunks)
  • libvirt/tests/src/incremental_backup/incremental_backup_lifecycle.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • libvirt/tests/cfg/incremental_backup/incremental_backup_lifecycle.cfg
🧰 Additional context used
🧬 Code graph analysis (1)
libvirt/tests/src/incremental_backup/incremental_backup_lifecycle.py (3)
libvirt/tests/src/graphics/graphics_functional.py (1)
  • restore (231-242)
libvirt/tests/src/passthrough/robustness/passthrough_robustness.py (2)
  • start (176-225)
  • stop (227-238)
provider/guest_os_booting/guest_os_booting_base.py (1)
  • get_vm (18-56)
🪛 Ruff (0.12.2)
libvirt/tests/src/incremental_backup/incremental_backup_lifecycle.py

22-22: Use of possibly insecure function; consider using ast.literal_eval

(S307)


27-27: Use of possibly insecure function; consider using ast.literal_eval

(S307)


29-29: Use of possibly insecure function; consider using ast.literal_eval

(S307)


60-60: Do not catch blind exception: Exception

(BLE001)


181-181: Function call with shell=True parameter identified, security issue

(S604)


203-203: Use of possibly insecure function; consider using ast.literal_eval

(S307)

Comment on lines +60 to +62
except Exception as details:
test.fail("Fail to get full backup data: %s" % details)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use specific exception handling instead of catching all exceptions.

Catching all exceptions with Exception can mask programming errors and make debugging difficult.

Replace the broad exception handling with specific exceptions:

-    except Exception as details:
-        test.fail("Fail to get full backup data: %s" % details)
+    except (OSError, IOError, RuntimeError) as details:
+        test.fail("Fail to get full backup data: %s" % details)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
except Exception as details:
test.fail("Fail to get full backup data: %s" % details)
except (OSError, IOError, RuntimeError) as details:
test.fail("Fail to get full backup data: %s" % details)
🧰 Tools
🪛 Ruff (0.12.2)

60-60: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
In libvirt/tests/src/incremental_backup/incremental_backup_lifecycle.py around
lines 60-61, the code currently catches a broad Exception when failing to get
full backup data; replace this with explicit exception handlers for the errors
you actually expect (for example libvirt.libvirtError for libvirt API failures,
ValueError/KeyError for parsing issues, OSError or subprocess.CalledProcessError
for I/O or external command failures). Handle each expected exception separately
to call test.fail with the error details, and allow any unexpected exceptions to
propagate (or re-raise them) so programming errors aren’t masked.


test.log.info("Stop libvirt daemon and kill qemu process.")
Libvirtd().stop()
process.run("kill -9 `pidof qemu-kvm`", shell=True, ignore_status=False)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Security risk: Avoid shell=True with process execution.

Using shell=True with process.run() creates a security vulnerability as it can be exploited through shell injection.

Use a safer approach without shell expansion:

-    process.run("kill -9 `pidof qemu-kvm`", shell=True, ignore_status=False)
+    # Get qemu-kvm PIDs and kill them
+    try:
+        pidof_result = process.run("pidof qemu-kvm", shell=False, ignore_status=True)
+        if pidof_result.exit_status == 0:
+            pids = pidof_result.stdout_text.strip().split()
+            for pid in pids:
+                process.run(["kill", "-9", pid], shell=False, ignore_status=False)
+    except Exception as e:
+        test.log.warning("Failed to kill qemu-kvm processes: %s" % e)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
process.run("kill -9 `pidof qemu-kvm`", shell=True, ignore_status=False)
# Get qemu-kvm PIDs and kill them
try:
pidof_result = process.run("pidof qemu-kvm", shell=False, ignore_status=True)
if pidof_result.exit_status == 0:
pids = pidof_result.stdout_text.strip().split() # stdout_text is provided by CmdResult([avocado-framework.readthedocs.io](https://avocado-framework.readthedocs.io/en/61.0/api/utils/avocado.utils.html?utm_source=openai))
for pid in pids:
process.run(["kill", "-9", pid], shell=False, ignore_status=False)
except Exception as e:
test.log.warning("Failed to kill qemu-kvm processes: %s" % e)
🧰 Tools
🪛 Ruff (0.12.2)

181-181: Function call with shell=True parameter identified, security issue

(S604)

🤖 Prompt for AI Agents
libvirt/tests/src/incremental_backup/incremental_backup_lifecycle.py around line
181: the current call uses process.run("kill -9 `pidof qemu-kvm`", shell=True)
which is a shell injection risk; replace it with a non-shell invocation such as
calling pkill directly (e.g. run ["pkill","-9","qemu-kvm"] with check/ignore
status as appropriate) or programmatically obtain PIDs by running pidof without
shell and then pass the numeric PIDs to kill (e.g. capture output from running
["pidof","qemu-kvm"], split into IDs, and call kill with a list of args),
ensuring shell=True is removed.

@meinaLi meinaLi changed the title Inc_backup: automate new cases about lifecycle Inc_backup: Automate incremental backup lifecycle test cases Sep 10, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (7)
libvirt/tests/src/incremental_backup/incremental_backup_lifecycle.py (7)

168-171: Fix typo in docstring (“exsiting” → “existing”).

-    Kill qemu process between libvirtd stop/start when there is an exsiting pull-mode backup job.
+    Kill qemu process between libvirtd stop/start when there is an existing pull-mode backup job.

204-205: Avoid global state for backup_file_list (pass state explicitly).

Globals hinder test isolation and readability.

Refactor tests to accept a backup_file_list (or a small state object) passed from run(), and return it, removing the global entirely.


1-1: Security: replace eval() with ast.literal_eval for config parsing.

Avoid executing arbitrary code when parsing params.

 import os
+import ast
@@
-    backup_dict = eval(params.get("backup_dict", "{}") % scratch_file)
+    backup_dict = ast.literal_eval(params.get("backup_dict", "{}") % scratch_file)
@@
-        checkpoint_dict = eval(params.get("checkpoint_dict") % inc_checkpoint)
+        checkpoint_dict = ast.literal_eval(params.get("checkpoint_dict") % inc_checkpoint)
@@
-        checkpoint_dict = eval(params.get("checkpoint_dict") % full_checkpoint)
+        checkpoint_dict = ast.literal_eval(params.get("checkpoint_dict") % full_checkpoint)

Also applies to: 22-29


58-61: Don’t catch broad Exception — handle expected errors explicitly.

Improve debuggability and avoid masking bugs.

-    except Exception as details:
+    except (OSError, IOError, RuntimeError) as details:
         test.fail("Fail to get full backup data: %s" % details)

76-88: Incremental backup: make disk size configurable, include protocol, and add error handling.

Prevents brittle tests and aligns with full-backup handling.

-    nbd_bitmap_name = "backup-" + target_disk
-    original_disk_size = "10G"
+    nbd_bitmap_name = "backup-" + target_disk
+    original_disk_size = params.get("original_disk_size", "10G")
@@
-    nbd_params = {
-        'nbd_hostname': nbd_hostname,
-        'nbd_tcp_port': nbd_tcp_port,
-        'nbd_export': target_disk
-        }
+    nbd_params = {
+        'nbd_protocol': "tcp",
+        'nbd_hostname': nbd_hostname,
+        'nbd_tcp_port': nbd_tcp_port,
+        'nbd_export': target_disk
+        }
@@
-    utils_backup.pull_incremental_backup_to_file(
-                        nbd_params, backup_file_path, nbd_bitmap_name,
-                        original_disk_size)
+    try:
+        utils_backup.pull_incremental_backup_to_file(
+            nbd_params, backup_file_path, nbd_bitmap_name, original_disk_size)
+    except (OSError, IOError, RuntimeError) as details:
+        test.fail("Fail to get incremental backup data: %s" % details)

179-185: Security: avoid shell=True; kill qemu-kvm safely.

Prevent shell injection; handle absence of processes gracefully.

-    Libvirtd().stop()
-    process.run("kill -9 `pidof qemu-kvm`", shell=True, ignore_status=False)
+    Libvirtd().stop()
+    # Get qemu-kvm PIDs and kill them without using a shell
+    pidof_res = process.run(["pidof", "qemu-kvm"], shell=False, ignore_status=True)
+    if pidof_res.exit_status == 0:
+        for pid in pidof_res.stdout_text.strip().split():
+            process.run(["kill", "-9", pid], shell=False, ignore_status=False)

201-206: Replace eval-based dispatcher with explicit mapping.

Eliminates code execution risk and clarifies allowed cases.

-    vmxml_backup = vmxml.copy()
-    run_test = eval('test_%s' % case)
-    global backup_file_list
-    backup_file_list = []
+    vmxml_backup = vmxml.copy()
+    test_functions = {
+        "save_vm": test_save_vm,
+        "managedsave": test_managedsave,
+        "restart_service": test_restart_service,
+        "kill_qemu_process": test_kill_qemu_process,
+    }
+    run_test = test_functions.get(case)
+    if not run_test:
+        test.error(f"Unknown test case: {case}")
+    global backup_file_list
+    backup_file_list = []
🧹 Nitpick comments (3)
libvirt/tests/src/incremental_backup/incremental_backup_lifecycle.py (3)

15-21: Fix docstring: return value mismatch.

Function returns only backup_options, not “backup options and the scratch file”.

 def prepare_backup_xml(test, params, backup_type):
     """
     Prepare the backup xml.
 
-    :params return: return the backup options and the scratch file.
+    :returns: backup options string to pass to virsh.backup_begin.
     """

104-113: Check domjobabort result and remove save file to avoid leftovers.

Strengthens assertions and keeps workspace clean.

-    virsh.domjobabort(vm_name)
+    abort_res = virsh.domjobabort(vm_name, debug=True)
+    libvirt.check_exit_status(abort_res)
@@
     virsh.restore(save_file, debug=True, ignore_status=False)
+    if os.path.exists(save_file):
+        os.remove(save_file)

137-143: Validate VM start result after managedsave.

Ensure guest actually starts.

-    virsh.start(vm_name)
+    start_res = virsh.start(vm_name, debug=True)
+    libvirt.check_exit_status(start_res)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d364035 and 6b660f8.

📒 Files selected for processing (2)
  • libvirt/tests/cfg/incremental_backup/incremental_backup_lifecycle.cfg (1 hunks)
  • libvirt/tests/src/incremental_backup/incremental_backup_lifecycle.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • libvirt/tests/cfg/incremental_backup/incremental_backup_lifecycle.cfg
🧰 Additional context used
🧬 Code graph analysis (1)
libvirt/tests/src/incremental_backup/incremental_backup_lifecycle.py (2)
libvirt/tests/src/graphics/graphics_functional.py (1)
  • restore (231-242)
provider/guest_os_booting/guest_os_booting_base.py (1)
  • get_vm (18-56)
🪛 Ruff (0.12.2)
libvirt/tests/src/incremental_backup/incremental_backup_lifecycle.py

22-22: Use of possibly insecure function; consider using ast.literal_eval

(S307)


27-27: Use of possibly insecure function; consider using ast.literal_eval

(S307)


29-29: Use of possibly insecure function; consider using ast.literal_eval

(S307)


60-60: Do not catch blind exception: Exception

(BLE001)


181-181: Function call with shell=True parameter identified, security issue

(S604)


203-203: Use of possibly insecure function; consider using ast.literal_eval

(S307)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Python 3.8
  • GitHub Check: Python 3.9
  • GitHub Check: Python 3.12
  • GitHub Check: Python 3.11

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (7)
libvirt/tests/src/incremental_backup/incremental_backup_lifecycle.py (7)

1-3: Missing import for ast.literal_eval.

Add ast import to support safe parsing.

 import os
+import ast

58-62: Avoid broad except; catch expected errors only.

Catching Exception masks bugs.

-    except Exception as details:
+    except (OSError, IOError, RuntimeError) as details:
         test.fail("Fail to get full backup data: %s" % details)

86-92: Avoid broad except in incremental backup as well.

Mirror the narrowed exceptions used in full backup.

-    except Exception as details:
+    except (OSError, IOError, RuntimeError) as details:
         test.fail("Fail to get incremental backup data: %s" % details)

95-101: Refactor: pass backup_file_list explicitly to test_ functions.*

Avoid globals; pass the list from run() to each test.

Apply these diffs:

-def test_save_vm(test, params):
+def test_save_vm(test, params, backup_file_list):
@@
-    backup_file_list.append(backup_file_path)
+    backup_file_list.append(backup_file_path)
-def test_managedsave(test, params):
+def test_managedsave(test, params, backup_file_list):
@@
-    backup_file_list.append(backup_file_path)
+    backup_file_list.append(backup_file_path)
-def test_restart_service(test, params):
+def test_restart_service(test, params, backup_file_list):
@@
-    return backup_file_list
+    return backup_file_list
-def test_kill_qemu_process(test, params):
+def test_kill_qemu_process(test, params, backup_file_list):
@@
-    backup_file_list.append(backup_file_path)
+    backup_file_list.append(backup_file_path)

And in run():

-        backup_file_list = run_test(test, params)
+        backup_file_list = run_test(test, params, backup_file_list)

Also applies to: 126-132, 152-158, 174-180, 216-217


21-29: Security: replace eval(...) with ast.literal_eval for config parsing.

Executing eval on params is unsafe. Use ast.literal_eval.

Apply this diff:

-    backup_dict = eval(params.get("backup_dict", "{}") % scratch_file)
+    backup_dict = ast.literal_eval(params.get("backup_dict", "{}") % scratch_file)
@@
-        checkpoint_dict = eval(params.get("checkpoint_dict") % inc_checkpoint)
+        checkpoint_dict = ast.literal_eval(params.get("checkpoint_dict") % inc_checkpoint)
@@
-        checkpoint_dict = eval(params.get("checkpoint_dict") % full_checkpoint)
+        checkpoint_dict = ast.literal_eval(params.get("checkpoint_dict") % full_checkpoint)

Add the import at the top (see separate comment).


185-188: Security: avoid shell=True; kill qemu processes safely.

shell=True with backticks is injection-prone. Use non-shell commands.

-    Libvirtd().stop()
-    process.run("kill -9 `pidof qemu-kvm`", shell=True, ignore_status=False)
+    Libvirtd().stop()
+    # Get qemu-kvm PIDs and kill them safely without a shell
+    pidof_result = process.run("pidof qemu-kvm", shell=False, ignore_status=True)
+    if pidof_result.exit_status == 0:
+        for pid in pidof_result.stdout_text.strip().split():
+            process.run(["kill", "-9", pid], shell=False, ignore_status=False)
+    else:
+        test.fail("pidof qemu-kvm returned no PIDs; guest may not be running as expected")

209-211: Security: replace dynamic eval dispatch and drop global state.

Use an explicit dispatch map and avoid global backup_file_list.

-    run_test = eval('test_%s' % case)
-    global backup_file_list
-    backup_file_list = []
+    test_functions = {
+        'save_vm': test_save_vm,
+        'managedsave': test_managedsave,
+        'restart_service': test_restart_service,
+        'kill_qemu_process': test_kill_qemu_process,
+    }
+    run_test = test_functions.get(case)
+    if not run_test:
+        test.error(f"Unknown test case: {case}")
+    backup_file_list = []

Follow-up: update test_* signatures to accept backup_file_list (see below) and pass it in run().

🧹 Nitpick comments (4)
libvirt/tests/src/incremental_backup/incremental_backup_lifecycle.py (4)

15-20: Docstring mismatch with return value.

Function returns only backup_options, not scratch file.

-    :return: return the backup options and the scratch file.
+    :return: backup options string for virsh.backup_begin.

56-58: Ensure backup_begin failures are not ignored.

Explicitly fail on non-zero exit.

-    virsh.backup_begin(vm_name, backup_options, debug=True)
+    result = virsh.backup_begin(vm_name, backup_options, debug=True, ignore_status=False)
+    libvirt.check_exit_status(result)

79-83: Include nbd_protocol for consistency.

Full backup passes nbd_protocol, incremental should too.

-    nbd_params = {
-        'nbd_hostname': nbd_hostname,
-        'nbd_tcp_port': nbd_tcp_port,
-        'nbd_export': target_disk
-        }
+    nbd_params = {
+        'nbd_protocol': "tcp",
+        'nbd_hostname': nbd_hostname,
+        'nbd_tcp_port': nbd_tcp_port,
+        'nbd_export': target_disk
+    }

141-142: Check domjobabort result.

Validate abort succeeded for consistency with test_save_vm.

-    virsh.domjobabort(vm_name)
+    abort_result = virsh.domjobabort(vm_name)
+    libvirt.check_exit_status(abort_result)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6b660f8 and 33b733b.

📒 Files selected for processing (2)
  • libvirt/tests/cfg/incremental_backup/incremental_backup_lifecycle.cfg (1 hunks)
  • libvirt/tests/src/incremental_backup/incremental_backup_lifecycle.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • libvirt/tests/cfg/incremental_backup/incremental_backup_lifecycle.cfg
🧰 Additional context used
🪛 Ruff (0.12.2)
libvirt/tests/src/incremental_backup/incremental_backup_lifecycle.py

22-22: Use of possibly insecure function; consider using ast.literal_eval

(S307)


27-27: Use of possibly insecure function; consider using ast.literal_eval

(S307)


29-29: Use of possibly insecure function; consider using ast.literal_eval

(S307)


60-60: Do not catch blind exception: Exception

(BLE001)


90-90: Do not catch blind exception: Exception

(BLE001)


187-187: Function call with shell=True parameter identified, security issue

(S604)


209-209: Use of possibly insecure function; consider using ast.literal_eval

(S307)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Python 3.12
  • GitHub Check: Python 3.9
  • GitHub Check: Python 3.11
  • GitHub Check: Python 3.8
🔇 Additional comments (2)
libvirt/tests/src/incremental_backup/incremental_backup_lifecycle.py (2)

78-78: Good: disk size is now configurable.

Reading original_disk_size from params improves flexibility.


176-176: Typo fixed in docstring.

“existing” spelling is correct now.

Copy link
Contributor

@dzhengfy dzhengfy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Others LGTM

backup_file_list.append(backup_file_path)

test.log.info("Do save before abort the backup job.")
save_file = data_dir.get_data_dir() + '/%s.save' % vm_name
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if data_dir.get_tmp_dir() can be used by you which does not need cleanup. But this is not required.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
libvirt/tests/src/incremental_backup/incremental_backup_lifecycle.py (1)

194-198: Avoid shell=True; kill qemu-kvm safely.

Replace shell pipeline with non-shell calls to prevent injection and improve robustness. (Previously flagged; reiterating.)

-    Libvirtd().stop()
-    process.run("kill -9 `pidof qemu-kvm`", shell=True, ignore_status=False)
+    Libvirtd().stop()
+    # Kill all qemu-kvm PIDs without using a shell
+    pidof_res = process.run(["pidof", "qemu-kvm"], ignore_status=True)
+    if pidof_res.exit_status == 0:
+        for pid in pidof_res.stdout_text.strip().split():
+            process.run(["kill", "-9", pid], ignore_status=False)
🧹 Nitpick comments (6)
libvirt/tests/cfg/incremental_backup/incremental_backup_lifecycle.cfg (3)

13-13: Remove unused inc_backup_dict (avoid config drift).

The Python test constructs incremental options programmatically; this dict is never consumed. Drop it to reduce confusion.

-    inc_backup_dict = {'mode': 'pull', 'incremental': '${full_checkpoint}', 'server': {'name': '${nbd_hostname}', 'port': '${nbd_tcp_port}'}, 'disks': [{'name': 'vda', 'backup': 'yes', 'type': 'file', 'scratch': {'attrs': {'file': '%s'}}}]}

16-20: Relax expected_error regex to be libvirt-version agnostic.

Embedding driver internals like remoteDispatchDomainBackupBegin/monitor is brittle across versions/transports. Match the stable text only.

-            expected_error = "Timed out during operation: cannot acquire state change lock.*remoteDispatchDomainBackupBegin"
+            expected_error = "Timed out during operation: cannot acquire state change lock"
-            expected_error = "Timed out during operation: cannot acquire state change lock.*remoteDispatchDomainBackupBegin"
+            expected_error = "Timed out during operation: cannot acquire state change lock"
-            expected_error = "Timed out during operation: cannot acquire state change lock.*monitor"
+            expected_error = "Timed out during operation: .*monitor"

10-10: Port collision risk on shared runners.

Hard-coding 10809 may clash if suites run in parallel. Consider making the port overridable per job (and document it), or auto-pick a free port.

libvirt/tests/src/incremental_backup/incremental_backup_lifecycle.py (3)

16-21: Docstring-return mismatch.

Function returns only backup_options, not “backup options and the scratch file.” Update the docstring or return the scratch path and clean it up later.

-    :return: return the backup options and the scratch file.
+    :return: backup options string for virsh.backup_begin.

60-62: Use narrower exceptions for clearer failures.

Catching Exception masks programming errors. Prefer CmdError/OS errors (and optionally a utils_backup-specific error if exposed).

-    except Exception as details:
+    except (process.CmdError, OSError, RuntimeError) as details:
         test.fail("Fail to get full backup data: %s" % details)
-    except Exception as details:
+    except (process.CmdError, OSError, RuntimeError) as details:
         test.fail("Fail to get incremental backup data: %s" % details)

Also applies to: 91-92


80-85: Add nbd_protocol to incremental pull (match full backup params).

Keep NBD params consistent; avoids hidden defaults changing behavior.

     nbd_params = {
+        'nbd_protocol': "tcp",
         'nbd_hostname': nbd_hostname,
         'nbd_tcp_port': nbd_tcp_port,
         'nbd_export': target_disk
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 33b733b and 2876a5b.

📒 Files selected for processing (2)
  • libvirt/tests/cfg/incremental_backup/incremental_backup_lifecycle.cfg (1 hunks)
  • libvirt/tests/src/incremental_backup/incremental_backup_lifecycle.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
libvirt/tests/src/incremental_backup/incremental_backup_lifecycle.py (1)
libvirt/tests/src/incremental_backup/incremental_backup_pull_mode_tls.py (2)
  • backup_begin (85-95)
  • pull_full_backup_to_file (123-142)
🪛 Ruff (0.14.1)
libvirt/tests/src/incremental_backup/incremental_backup_lifecycle.py

61-61: Do not catch blind exception: Exception

(BLE001)


91-91: Do not catch blind exception: Exception

(BLE001)


196-196: Function call with shell=True parameter identified, security issue

(S604)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Python 3.12
  • GitHub Check: Python 3.9
  • GitHub Check: Python 3.11
  • GitHub Check: Python 3.8
🔇 Additional comments (3)
libvirt/tests/src/incremental_backup/incremental_backup_lifecycle.py (3)

22-31: Good move: ast.literal_eval for config dicts.

Safer than eval; avoids arbitrary code execution.


219-227: Good move: explicit dispatch map.

Removes unsafe eval-based dispatch; clearer and safer.


106-116: Verify backup job is active before save/abort operations; add domjobinfo check.

The test lacks verification that the backup job is running when save/abort are called. Other similar tests in the codebase (incremental_backup_pull_mode.py:375–377, incremental_backup_fd_transport.py:81–82) verify job status with domjobinfo before performing concurrent operations. Without this check, if pull_full_backup_to_file blocks until completion (likely, given the synchronous call pattern), the save and abort operations run after the job ends, defeating the concurrency test.

Add domjobinfo verification immediately after start_full_backup() returns to confirm "Backup" is in the job status before proceeding with save/abort. This also applies to the test_managedsave function at lines 143–147.

Copy link
Contributor

@smitterl smitterl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Smaller nitpicks. Rest LGTM, thank you.

Automate cases:
RHEL-187642 pull mode backup and managed save vm together
RHEL-187641 pull mode backup and save vm together
RHEL-199045 Start an existing backup job after libvirtd restarted
RHEL-199046 kill qemu process between libvirtd stop/start when there is an existing pull-mode backup job

Signed-off-by: Meina Li <[email protected]>
@meinaLi meinaLi requested a review from smitterl October 31, 2025 03:46
@autotest autotest deleted a comment from coderabbitai bot Nov 3, 2025
Copy link
Contributor

@smitterl smitterl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you

@dzhengfy dzhengfy merged commit f3496fb into autotest:master Nov 4, 2025
6 checks passed
@meinaLi meinaLi deleted the inc_lifecycle branch November 4, 2025 09:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants