diff --git a/v2v/tests/cfg/nbdkit/nbdkit.cfg b/v2v/tests/cfg/nbdkit/nbdkit.cfg index 5b14f47198..2270fef213 100644 --- a/v2v/tests/cfg/nbdkit/nbdkit.cfg +++ b/v2v/tests/cfg/nbdkit/nbdkit.cfg @@ -148,19 +148,27 @@ external_image_url = EXTERNAL_IMAGE_FAKE_URL - nbdcopy: only source_none..dest_none - version_required = "[nbdkit-server-1.42.2-4,);[libnbd-1.22.2-2,)" - disk_path = DEFAULT_TEST_IMAGE - checkpoint = 'check_blkhash_option' variants: - blkhash: - variants: - - positive_test: - test_state= 'positive' - hash_option= 'md5/512k' - - negative_test: - test_state= 'negative' - hash_option= 'sha1/100k' - expected_warn_msg= 'nbdcopy: --blkhash is not a power of two: 100k' + version_required = "[nbdkit-server-1.42.2-4,);[libnbd-1.22.2-2,)" + disk_path = DEFAULT_TEST_IMAGE + checkpoint = 'check_blkhash_option' + variants: + - positive_test: + test_state= 'positive' + hash_option= 'md5/512k' + - negative_test: + test_state= 'negative' + hash_option= 'sha1/100k' + expected_warn_msg= 'nbdcopy: --blkhash is not a power of two: 100k' + - data_integrity: + checkpoint = 'check_data_integrity' + version_required = "[nbdkit-1.46,)" + variants: + - positive_test: + test_state= 'positive' + - negative_test: + test_state= 'negative' - blocksize: only source_none..dest_none version_required = "[nbdkit-1.38.0-1,)" diff --git a/v2v/tests/src/nbdkit/nbdkit.py b/v2v/tests/src/nbdkit/nbdkit.py index c972749ff0..75e8e95c59 100644 --- a/v2v/tests/src/nbdkit/nbdkit.py +++ b/v2v/tests/src/nbdkit/nbdkit.py @@ -809,6 +809,68 @@ def test_nbdkit_instance_name(): # Force kill if it's being stubborn p.kill() + def test_nbd_data_integrity(): + from subprocess import Popen, PIPE, STDOUT + import time + # --- Configuration --- + SOCKET = "/tmp/test.sock" + IMG_PATH = os.path.join(data_dir.get_tmp_dir(), "test.img") + IMAGE_SIZE = "1G" + + LOG.info(f"Prepare {IMAGE_SIZE} image file") + cmd = f"truncate -s {IMAGE_SIZE} {IMG_PATH}" + process.run(cmd, shell=True, ignore_status=True) + if os.path.exists(SOCKET): + os.remove(SOCKET) + + LOG.info("Starting nbdkit server in background") + nbd_log_file = os.path.join(data_dir.get_tmp_dir(), "nbd_log_file.log") + # Start nbdkit with debug flags enabled + with open(nbd_log_file, "w") as nbd_log_file: + server_cmd = ["nbdkit", "-f", "-v", "-D", "file.zero=1", "-U", SOCKET, "file", IMG_PATH] + nbd_server = Popen(server_cmd, stdout=nbd_log_file, stderr=nbd_log_file) + # Wait for the socket file to appear + time.sleep(1) + + test_state = params_get(params, "test_state") + try: + if test_state == "positive": + LOG.info("Testing nbdcopy with 32M") + cmd = f"nbdcopy --request-size=32M /dev/zero nbd+unix:///?socket={SOCKET}" + result = process.run(cmd, shell=True, ignore_status=True) + exp_warn = "No space left on device" + if exp_warn not in result.stderr_text: + test.fail(f"Expected warning {exp_warn}, got {result.stderr_text}") + + LOG.info("Verification (Compare with /dev/zero)") + cmd = f"nbdcopy nbd+unix:///?socket={SOCKET} - | cmp -n {IMAGE_SIZE} - /dev/zero" + result = process.run(cmd, shell=True, ignore_status=True) + + if result.exit_status != 0: + test.fail(f"Check Failed: Mismatch found! {result.stderr_text}") + + LOG.info("Verification (MD5 Hash Check)") + # Pipe the output directly into a hash to save memory + hash_cmd = f"nbdcopy nbd+unix:///?socket={SOCKET} - | head -c {IMAGE_SIZE} | md5sum" + result = process.run(hash_cmd, shell=True, ignore_status=True) + actual_md5 = result.stdout_text + result = process.run("head -c 1G /dev/zero | md5sum", shell=True, ignore_status=True) + expected_md5 = result.stdout_text + if actual_md5 != expected_md5: + test.fail("[FAILURE] Data corruption detected.") + elif test_state == "negative": + LOG.info("Testing nbdcopy with 128M (Expected Failure)") + cmd = f"nbdcopy --request-size=128M /dev/zero nbd+unix:///?socket={SOCKET}" + result = process.run(cmd, shell=True, ignore_status=True) + err_msg = "must be a power of 2 within 4096-33554432: 128M" + if err_msg not in result.stderr_text: + test.fail(f"Expected warning {err_msg}, got {result.stderr_text}") + finally: + LOG.info("Cleaning up") + nbd_server.terminate() + nbd_server.wait() + if os.path.exists(SOCKET): + os.remove(SOCKET) def test_count_filter(): # p is our SubProcess object p = None @@ -969,6 +1031,8 @@ def test_data_plugin_supports_base64_in_format_string(): check_blocksize_constraints() elif checkpoint == 'test_nbdkit_instance_name': test_nbdkit_instance_name() + elif checkpoint == 'check_data_integrity': + test_nbd_data_integrity() elif checkpoint == 'test_count_filter': test_count_filter() elif checkpoint == 'test_data_plugin_supports_base64_in_format_string':