From a6c6b6e0f2123526f0f92503e72d4ce8d50daf6a Mon Sep 17 00:00:00 2001 From: upstream Date: Thu, 8 Oct 2026 01:24:03 +0200 Subject: [PATCH] Upstream proxmox-git/qemu-server state of the touched files before 07b2af3b17 --- src/test/MigrationTest/QemuMigrateMock.pm | 408 ++++++++++++++++++++++ src/test/run_qemu_migrate_tests.pl | 47 --- 2 files changed, 408 insertions(+), 47 deletions(-) create mode 100644 src/test/MigrationTest/QemuMigrateMock.pm diff --git a/src/test/MigrationTest/QemuMigrateMock.pm b/src/test/MigrationTest/QemuMigrateMock.pm new file mode 100644 index 0000000..3a48381 --- /dev/null +++ b/src/test/MigrationTest/QemuMigrateMock.pm @@ -0,0 +1,408 @@ +package MigrationTest::QemuMigrateMock; + +use strict; +use warnings; + +use JSON; +use Test::MockModule; + +use MigrationTest::Shared; + +use PVE::API2::Qemu; +use PVE::QemuServer::Drive; +use PVE::Storage; +use PVE::Tools qw(file_set_contents file_get_contents); + +use PVE::CLIHandler; +use base qw(PVE::CLIHandler); + +my $RUN_DIR_PATH = $ENV{RUN_DIR_PATH} or die "no RUN_DIR_PATH set\n"; +my $QM_LIB_PATH = $ENV{QM_LIB_PATH} or die "no QM_LIB_PATH set\n"; + +my $source_volids = decode_json(file_get_contents("${RUN_DIR_PATH}/source_volids")); +my $source_vdisks = decode_json(file_get_contents("${RUN_DIR_PATH}/source_vdisks")); +my $vm_status = decode_json(file_get_contents("${RUN_DIR_PATH}/vm_status")); +my $expected_calls = decode_json(file_get_contents("${RUN_DIR_PATH}/expected_calls")); +my $fail_config = decode_json(file_get_contents("${RUN_DIR_PATH}/fail_config")); +my $storage_migrate_map = decode_json(file_get_contents("${RUN_DIR_PATH}/storage_migrate_map")); +my $migrate_params = decode_json(file_get_contents("${RUN_DIR_PATH}/migrate_params")); + +my $test_vmid = $migrate_params->{vmid}; +my $test_target = $migrate_params->{target}; +my $test_opts = $migrate_params->{opts}; +my $current_log = ''; + +my $vm_stop_executed = 0; + +# mocked modules + +my $inotify_module = Test::MockModule->new("PVE::INotify"); +$inotify_module->mock( + nodename => sub { + return 'pve0'; + }, +); + +$MigrationTest::Shared::qemu_config_module->mock( + move_config_to_node => sub { + my ($self, $vmid, $target) = @_; + die "moving wrong config: '$vmid'\n" if $vmid ne $test_vmid; + die "moving config to wrong node: '$target'\n" if $target ne $test_target; + delete $expected_calls->{move_config_to_node}; + }, +); + +my $tunnel_module = Test::MockModule->new("PVE::Tunnel"); +$tunnel_module->mock( + finish_tunnel => sub { + delete $expected_calls->{'finish_tunnel'}; + return; + }, + write_tunnel => sub { + my ($tunnel, $timeout, $command) = @_; + + if ($command =~ m/^resume (\d+)$/) { + my $vmid = $1; + die "resuming wrong VM '$vmid'\n" if $vmid ne $test_vmid; + return; + } + die "write_tunnel (mocked) - implement me: $command\n"; + }, +); + +my $qemu_migrate_module = Test::MockModule->new("PVE::QemuMigrate"); +$qemu_migrate_module->mock( + fork_tunnel => sub { + die "fork_tunnel (mocked) - implement me\n"; # currently no call should lead here + }, + start_remote_tunnel => sub { + my ($self, $raddr, $rport, $ruri, $unix_socket_info) = @_; + $expected_calls->{'finish_tunnel'} = 1; + $self->{tunnel} = { + writer => "mocked", + reader => "mocked", + pid => 123456, + version => 1, + }; + }, + log => sub { + my ($self, $level, $message) = @_; + $current_log .= "$level: $message\n"; + }, + mon_cmd => sub { + my ($vmid, $command, %params) = @_; + + if ($command eq 'nbd-server-start') { + return; + } elsif ($command eq 'block-dirty-bitmap-add') { + my $drive = $params{node}; + delete $expected_calls->{"block-dirty-bitmap-add-${drive}"}; + return; + } elsif ($command eq 'block-dirty-bitmap-remove') { + return; + } elsif ($command eq 'query-migrate') { + return { status => 'failed' } if $fail_config->{'query-migrate'}; + return { status => 'completed' }; + } elsif ($command eq 'migrate') { + return; + } elsif ($command eq 'migrate-set-parameters') { + return; + } elsif ($command eq 'migrate_cancel') { + return; + } + die "mon_cmd (mocked) - implement me: $command"; + }, + transfer_replication_state => sub { + delete $expected_calls->{transfer_replication_state}; + }, + switch_replication_job_target => sub { + delete $expected_calls->{switch_replication_job_target}; + }, +); + +$MigrationTest::Shared::qemu_server_module->mock( + kvm_user_version => sub { + return "5.0.0"; + }, + vm_stop => sub { + $vm_stop_executed = 1; + delete $expected_calls->{'vm_stop'}; + }, +); + +my sub common_mirror_mock { + my ($vmid, $drive_id) = @_; + + die "drive_mirror with wrong vmid: '$vmid'\n" if $vmid ne $test_vmid; + die "qemu_drive_mirror '$drive_id' error\n" + if $fail_config->{qemu_drive_mirror} && $fail_config->{qemu_drive_mirror} eq $drive_id; + + my $nbd_info = decode_json(file_get_contents("${RUN_DIR_PATH}/nbd_info")); + die "target does not expect drive mirror for '$drive_id'\n" + if !defined($nbd_info->{$drive_id}); + delete $nbd_info->{$drive_id}; + file_set_contents("${RUN_DIR_PATH}/nbd_info", to_json($nbd_info)); +} + +my $qemu_server_blockjob_module = Test::MockModule->new("PVE::QemuServer::BlockJob"); +$qemu_server_blockjob_module->mock( + qemu_blockjobs_cancel => sub { + return; + }, + qemu_drive_mirror => sub { + my ( + $vmid, + $drive_id, + $dst_volid, + $vmiddst, + $is_zero_initialized, + $jobs, + $completion, + $qga, + $bwlimit, + $src_bitmap, + ) = @_; + + common_mirror_mock($vmid, $drive_id); + }, + blockdev_mirror => sub { + my ($source, $dest, $jobs, $completion, $options) = @_; + + my $drive_id = PVE::QemuServer::Drive::get_drive_id($source->{drive}); + + common_mirror_mock($source->{vmid}, $drive_id); + }, + monitor => sub { + my ($qmp_peer, $vmiddst, $jobs, $completion, $qga, $op) = @_; + + if ( + $fail_config->{block_job_monitor} + && $fail_config->{block_job_monitor} eq $completion + ) { + die "block_job_monitor '$completion' error\n"; + } + return; + }, + qemu_drive_mirror_switch_to_active_mode => sub { + return; + }, +); + +my $qemu_server_cpuconfig_module = Test::MockModule->new("PVE::QemuServer::CPUConfig"); +$qemu_server_cpuconfig_module->mock( + get_cpu_from_running_vm => sub { + die "invalid test: if you specify a custom CPU model you need to " + . "specify runningcpu as well\n" + if !defined($vm_status->{runningcpu}); + return $vm_status->{runningcpu}; + }, +); + +my $qemu_server_helpers_module = Test::MockModule->new("PVE::QemuServer::Helpers"); +$qemu_server_helpers_module->mock( + vm_running_locally => sub { + return $vm_status->{running} && !$vm_stop_executed; + }, +); + +my $qemu_server_machine_module = Test::MockModule->new("PVE::QemuServer::Machine"); +$qemu_server_machine_module->mock( + qemu_machine_pxe => sub { + die "invalid test: no runningmachine specified\n" + if !defined($vm_status->{runningmachine}); + return $vm_status->{runningmachine}; + }, + get_current_qemu_machine => sub { + die "invalid test: no runningmachine specified\n" + if !defined($vm_status->{runningmachine}); + return $vm_status->{runningmachine}; + }, +); + +my $qemu_server_network_module = Test::MockModule->new("PVE::QemuServer::Network"); +$qemu_server_network_module->mock( + del_nets_bridge_fdb => sub { return; }, + mon_cmd => sub { + my ($vmid, $command, %params) = @_; + + if ($command eq 'qom-get') { + if ( + $params{path} =~ m|^/machine/peripheral/net\d+$| + && $params{property} eq 'host_mtu' + ) { + return 1500; + } + die "mon_cmd (mocked) - implement me: $command for path '$params{path}' property" + . " '$params{property}'"; + } + die "mon_cmd (mocked) - implement me: $command"; + }, +); + +my $qemu_server_qmphelpers_module = Test::MockModule->new("PVE::QemuServer::QMPHelpers"); +$qemu_server_qmphelpers_module->mock( + runs_at_least_qemu_version => sub { + return 1; + }, +); + +my $ssh_info_module = Test::MockModule->new("PVE::SSHInfo"); +$ssh_info_module->mock( + get_ssh_info => sub { + my ($node, $network_cidr) = @_; + return { + ip => '1.2.3.4', + name => $node, + network => $network_cidr, + }; + }, +); + +$MigrationTest::Shared::storage_module->mock( + storage_migrate => sub { + my ($cfg, $volid, $target_sshinfo, $target_storeid, $opts, $logfunc) = @_; + + die "storage_migrate '$volid' error\n" + if $fail_config->{storage_migrate} && $fail_config->{storage_migrate} eq $volid; + + my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid); + + die "invalid test: need to add entry for '$volid' to storage_migrate_map\n" + if $storeid ne $target_storeid && !defined($storage_migrate_map->{$volid}); + + my $target_volname = $storage_migrate_map->{$volid} // $opts->{target_volname} + // $volname; + my $target_volid = "${target_storeid}:${target_volname}"; + MigrationTest::Shared::add_target_volid($target_volid); + + return $target_volid; + }, + vdisk_list => sub { # expects vmid to be set + my ($cfg, $storeid, $vmid, $vollist) = @_; + + my @storeids = defined($storeid) ? ($storeid) : keys %{$source_vdisks}; + + my $res = {}; + foreach my $storeid (@storeids) { + my $list_for_storeid = $source_vdisks->{$storeid}; + my @list_for_vm = grep { $_->{vmid} eq $vmid } @{$list_for_storeid}; + $res->{$storeid} = \@list_for_vm; + } + return $res; + }, + vdisk_free => sub { + my ($scfg, $volid) = @_; + + PVE::Storage::parse_volume_id($volid); + + die "vdisk_free '$volid' error\n" + if defined($fail_config->{vdisk_free}) && $fail_config->{vdisk_free} eq $volid; + + delete $source_volids->{$volid}; + }, + volume_size_info => sub { + my ($scfg, $volid) = @_; + my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid); + + for my $v ($source_vdisks->{$storeid}->@*) { + return wantarray ? ($v->{size}, $v->{format}, $v->{used}, $v->{parent}) : $v->{size} + if $v->{volid} eq $volid; + } + die "could not find '$volid' in mock 'source_vdisks'\n"; + }, +); + +$MigrationTest::Shared::tools_module->mock( + get_host_address_family => sub { + die "get_host_address_family (mocked) - implement me\n"; # currently no call should lead here + }, + next_migrate_port => sub { + die "next_migrate_port (mocked) - implement me\n"; # currently no call should lead here + }, + run_command => sub { + my ($cmd_tail, %param) = @_; + + my $cmd_msg = to_json($cmd_tail); + + my $cmd = shift @{$cmd_tail}; + + if ($cmd =~ m@^(?:/usr/bin/)?ssh$@) { + while (scalar(@{$cmd_tail})) { + $cmd = shift @{$cmd_tail}; + if ($cmd eq '/bin/true') { + return 0; + } elsif ($cmd eq 'qm') { + $cmd = shift @{$cmd_tail}; + if ($cmd eq 'start') { + delete $expected_calls->{ssh_qm_start}; + + delete $vm_status->{runningmachine}; + delete $vm_status->{runningcpu}; + + my @options = (@{$cmd_tail}); + while (scalar(@options)) { + my $opt = shift @options; + if ($opt eq '--machine') { + $vm_status->{runningmachine} = shift @options; + } elsif ($opt eq '--force-cpu') { + $vm_status->{runningcpu} = shift @options; + } + } + + return $MigrationTest::Shared::tools_module->original('run_command')->( + [ + '/usr/bin/perl', + "-I${QM_LIB_PATH}", + "-I${QM_LIB_PATH}/test", + "${QM_LIB_PATH}/test/MigrationTest/QmMock.pm", + 'start', + @{$cmd_tail}, + ], + %param, + ); + + } elsif ($cmd eq 'nbdstop') { + delete $expected_calls->{ssh_nbdstop}; + return 0; + } elsif ($cmd eq 'resume') { + return 0; + } elsif ($cmd eq 'unlock') { + my $vmid = shift @{$cmd_tail}; + die "unlocking wrong vmid: $vmid\n" if $vmid ne $test_vmid; + PVE::QemuConfig->remove_lock($vmid); + return 0; + } elsif ($cmd eq 'stop') { + return 0; + } + die "run_command (mocked) ssh qm command - implement me: ${cmd_msg}"; + } elsif ($cmd eq 'pvesm') { + $cmd = shift @{$cmd_tail}; + if ($cmd eq 'free') { + my $volid = shift @{$cmd_tail}; + PVE::Storage::parse_volume_id($volid); + return 1 + if $fail_config->{ssh_pvesm_free} + && $fail_config->{ssh_pvesm_free} eq $volid; + MigrationTest::Shared::remove_target_volid($volid); + return 0; + } + die "run_command (mocked) ssh pvesm command - implement me: ${cmd_msg}"; + } + } + die "run_command (mocked) ssh command - implement me: ${cmd_msg}"; + } + die "run_command (mocked) - implement me: ${cmd_msg}"; + }, +); + +eval { PVE::QemuMigrate->migrate($test_target, undef, $test_vmid, $test_opts) }; +my $error = $@; + +file_set_contents("${RUN_DIR_PATH}/source_volids", to_json($source_volids)); +file_set_contents("${RUN_DIR_PATH}/vm_status", to_json($vm_status)); +file_set_contents("${RUN_DIR_PATH}/expected_calls", to_json($expected_calls)); +file_set_contents("${RUN_DIR_PATH}/log", $current_log); + +die $error if $error; + +1; diff --git a/src/test/run_qemu_migrate_tests.pl b/src/test/run_qemu_migrate_tests.pl index 49248ed..05eed1d 100644 --- a/src/test/run_qemu_migrate_tests.pl +++ b/src/test/run_qemu_migrate_tests.pl @@ -37,7 +37,6 @@ my $storage_config = { local => { content => { images => 1, - iso => 1, }, path => "/var/lib/vz", type => "dir", @@ -347,17 +346,6 @@ my $source_vdisks = { 'volid' => 'local-dir:4567/vm-4567-state-snap2.raw', }, ], - 'local' => [ - { - 'ctime' => 1589439681, - 'format' => 'iso', - 'parent' => undef, - 'size' => 663748608, - 'used' => 663748608, - 'vmid' => '0', - 'volid' => 'local:iso/debian.iso', - }, - ], 'local-lvm' => [ { 'ctime' => '1589277334', @@ -1248,41 +1236,6 @@ my $tests = [ }, }, }, - { - name => '105_local_iso_in_snapshot', - target => 'pve1', - vmid => 105, - vm_status => { - running => 0, - }, - config_patch => { - snapshots => { - ohsnap => { - ide2 => 'local:iso/debian.iso,media=cdrom', - }, - }, - }, - expected_calls => {}, - expect_die => - "can't migrate local disk 'local:iso/debian.iso': local cdrom image (referenced in snapshot - ohsnap)", - expected => { - source_volids => local_volids_for_vm(105), - target_volids => {}, - vm_config => get_patched_config( - 105, - { - snapshots => { - ohsnap => { - ide2 => 'local:iso/debian.iso,media=cdrom', - }, - }, - }, - ), - vm_status => { - running => 0, - }, - }, - }, { name => '105_cdrom', target => 'pve1',