mirror of
				https://git.proxmox.com/git/qemu-server
				synced 2025-10-31 10:48:44 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			338 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
			
		
		
	
	
			338 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
| package MigrationTest::QemuMigrateMock;
 | |
| 
 | |
| use strict;
 | |
| use warnings;
 | |
| 
 | |
| use JSON;
 | |
| use Test::MockModule;
 | |
| 
 | |
| use MigrationTest::Shared;
 | |
| 
 | |
| use PVE::API2::Qemu;
 | |
| 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
 | |
|     },
 | |
|     read_tunnel => sub {
 | |
| 	die "read_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";
 | |
|     },
 | |
|     qemu_blockjobs_cancel => sub {
 | |
| 	return;
 | |
|     },
 | |
|     qemu_drive_mirror => sub {
 | |
| 	my ($vmid, $drive, $dst_volid, $vmiddst, $is_zero_initialized, $jobs, $completion, $qga, $bwlimit, $src_bitmap) = @_;
 | |
| 
 | |
| 	die "drive_mirror with wrong vmid: '$vmid'\n" if $vmid ne $test_vmid;
 | |
| 	die "qemu_drive_mirror '$drive' error\n"
 | |
| 	    if $fail_config->{qemu_drive_mirror} && $fail_config->{qemu_drive_mirror} eq $drive;
 | |
| 
 | |
| 	my $nbd_info = decode_json(file_get_contents("${RUN_DIR_PATH}/nbd_info"));
 | |
| 	die "target does not expect drive mirror for '$drive'\n"
 | |
| 	    if !defined($nbd_info->{$drive});
 | |
| 	delete $nbd_info->{$drive};
 | |
| 	file_set_contents("${RUN_DIR_PATH}/nbd_info", to_json($nbd_info));
 | |
|     },
 | |
|     qemu_drive_mirror_monitor => sub {
 | |
| 	my ($vmid, $vmiddst, $jobs, $completion, $qga) = @_;
 | |
| 
 | |
| 	if ($fail_config->{qemu_drive_mirror_monitor}
 | |
| 	    && $fail_config->{qemu_drive_mirror_monitor} eq $completion
 | |
| 	) {
 | |
| 	    die "qemu_drive_mirror_monitor '$completion' error\n";
 | |
| 	}
 | |
| 	return;
 | |
|     },
 | |
|     set_migration_caps => sub {
 | |
| 	return;
 | |
|     },
 | |
|     vm_stop => sub {
 | |
| 	$vm_stop_executed = 1;
 | |
| 	delete $expected_calls->{'vm_stop'};
 | |
|     },
 | |
|     del_nets_bridge_fdb => 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};
 | |
|     },
 | |
| );
 | |
| 
 | |
| 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};
 | |
|     },
 | |
| );
 | |
| 
 | |
| $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;
 | 
