]> git.proxmox.com Git - qemu-server.git/blob - test/MigrationTest/QemuMigrateMock.pm
94fe686f67f78f5082ffee291e7cc83e62e096e8
[qemu-server.git] / test / MigrationTest / QemuMigrateMock.pm
1 package MigrationTest::QemuMigrateMock;
2
3 use strict;
4 use warnings;
5
6 use JSON;
7 use Test::MockModule;
8
9 use MigrationTest::Shared;
10
11 use PVE::API2::Qemu;
12 use PVE::Storage;
13 use PVE::Tools qw(file_set_contents file_get_contents);
14
15 use PVE::CLIHandler;
16 use base qw(PVE::CLIHandler);
17
18 my $RUN_DIR_PATH = $ENV{RUN_DIR_PATH} or die "no RUN_DIR_PATH set\n";
19 my $QM_LIB_PATH = $ENV{QM_LIB_PATH} or die "no QM_LIB_PATH set\n";
20
21 my $source_volids = decode_json(file_get_contents("${RUN_DIR_PATH}/source_volids"));
22 my $source_vdisks = decode_json(file_get_contents("${RUN_DIR_PATH}/source_vdisks"));
23 my $vm_status = decode_json(file_get_contents("${RUN_DIR_PATH}/vm_status"));
24 my $expected_calls = decode_json(file_get_contents("${RUN_DIR_PATH}/expected_calls"));
25 my $fail_config = decode_json(file_get_contents("${RUN_DIR_PATH}/fail_config"));
26 my $storage_migrate_map = decode_json(file_get_contents("${RUN_DIR_PATH}/storage_migrate_map"));
27 my $migrate_params = decode_json(file_get_contents("${RUN_DIR_PATH}/migrate_params"));
28
29 my $test_vmid = $migrate_params->{vmid};
30 my $test_target = $migrate_params->{target};
31 my $test_opts = $migrate_params->{opts};
32 my $current_log = '';
33
34 my $vm_stop_executed = 0;
35
36 # mocked modules
37
38 my $inotify_module = Test::MockModule->new("PVE::INotify");
39 $inotify_module->mock(
40 nodename => sub {
41 return 'pve0';
42 },
43 );
44
45 $MigrationTest::Shared::qemu_config_module->mock(
46 move_config_to_node => sub {
47 my ($self, $vmid, $target) = @_;
48 die "moving wrong config: '$vmid'\n" if $vmid ne $test_vmid;
49 die "moving config to wrong node: '$target'\n" if $target ne $test_target;
50 delete $expected_calls->{move_config_to_node};
51 },
52 );
53
54 my $tunnel_module = Test::MockModule->new("PVE::Tunnel");
55 $tunnel_module->mock(
56 finish_tunnel => sub {
57 delete $expected_calls->{'finish_tunnel'};
58 return;
59 },
60 write_tunnel => sub {
61 my ($tunnel, $timeout, $command) = @_;
62
63 if ($command =~ m/^resume (\d+)$/) {
64 my $vmid = $1;
65 die "resuming wrong VM '$vmid'\n" if $vmid ne $test_vmid;
66 return;
67 }
68 die "write_tunnel (mocked) - implement me: $command\n";
69 },
70 );
71
72 my $qemu_migrate_module = Test::MockModule->new("PVE::QemuMigrate");
73 $qemu_migrate_module->mock(
74 fork_tunnel => sub {
75 die "fork_tunnel (mocked) - implement me\n"; # currently no call should lead here
76 },
77 read_tunnel => sub {
78 die "read_tunnel (mocked) - implement me\n"; # currently no call should lead here
79 },
80 start_remote_tunnel => sub {
81 my ($self, $raddr, $rport, $ruri, $unix_socket_info) = @_;
82 $expected_calls->{'finish_tunnel'} = 1;
83 $self->{tunnel} = {
84 writer => "mocked",
85 reader => "mocked",
86 pid => 123456,
87 version => 1,
88 };
89 },
90 log => sub {
91 my ($self, $level, $message) = @_;
92 $current_log .= "$level: $message\n";
93 },
94 mon_cmd => sub {
95 my ($vmid, $command, %params) = @_;
96
97 if ($command eq 'nbd-server-start') {
98 return;
99 } elsif ($command eq 'block-dirty-bitmap-add') {
100 my $drive = $params{node};
101 delete $expected_calls->{"block-dirty-bitmap-add-${drive}"};
102 return;
103 } elsif ($command eq 'block-dirty-bitmap-remove') {
104 return;
105 } elsif ($command eq 'query-migrate') {
106 return { status => 'failed' } if $fail_config->{'query-migrate'};
107 return { status => 'completed' };
108 } elsif ($command eq 'migrate') {
109 return;
110 } elsif ($command eq 'migrate-set-parameters') {
111 return;
112 } elsif ($command eq 'migrate_cancel') {
113 return;
114 }
115 die "mon_cmd (mocked) - implement me: $command";
116 },
117 transfer_replication_state => sub {
118 delete $expected_calls->{transfer_replication_state};
119 },
120 switch_replication_job_target => sub {
121 delete $expected_calls->{switch_replication_job_target};
122 },
123 );
124
125 $MigrationTest::Shared::qemu_server_module->mock(
126 kvm_user_version => sub {
127 return "5.0.0";
128 },
129 qemu_blockjobs_cancel => sub {
130 return;
131 },
132 qemu_drive_mirror => sub {
133 my ($vmid, $drive, $dst_volid, $vmiddst, $is_zero_initialized, $jobs, $completion, $qga, $bwlimit, $src_bitmap) = @_;
134
135 die "drive_mirror with wrong vmid: '$vmid'\n" if $vmid ne $test_vmid;
136 die "qemu_drive_mirror '$drive' error\n"
137 if $fail_config->{qemu_drive_mirror} && $fail_config->{qemu_drive_mirror} eq $drive;
138
139 my $nbd_info = decode_json(file_get_contents("${RUN_DIR_PATH}/nbd_info"));
140 die "target does not expect drive mirror for '$drive'\n"
141 if !defined($nbd_info->{$drive});
142 delete $nbd_info->{$drive};
143 file_set_contents("${RUN_DIR_PATH}/nbd_info", to_json($nbd_info));
144 },
145 qemu_drive_mirror_monitor => sub {
146 my ($vmid, $vmiddst, $jobs, $completion, $qga) = @_;
147
148 if ($fail_config->{qemu_drive_mirror_monitor}
149 && $fail_config->{qemu_drive_mirror_monitor} eq $completion
150 ) {
151 die "qemu_drive_mirror_monitor '$completion' error\n";
152 }
153 return;
154 },
155 set_migration_caps => sub {
156 return;
157 },
158 vm_stop => sub {
159 $vm_stop_executed = 1;
160 delete $expected_calls->{'vm_stop'};
161 },
162 del_nets_bridge_fdb => sub { return; },
163 );
164
165 my $qemu_server_cpuconfig_module = Test::MockModule->new("PVE::QemuServer::CPUConfig");
166 $qemu_server_cpuconfig_module->mock(
167 get_cpu_from_running_vm => sub {
168 die "invalid test: if you specify a custom CPU model you need to " .
169 "specify runningcpu as well\n" if !defined($vm_status->{runningcpu});
170 return $vm_status->{runningcpu};
171 }
172 );
173
174 my $qemu_server_helpers_module = Test::MockModule->new("PVE::QemuServer::Helpers");
175 $qemu_server_helpers_module->mock(
176 vm_running_locally => sub {
177 return $vm_status->{running} && !$vm_stop_executed;
178 },
179 );
180
181 my $qemu_server_machine_module = Test::MockModule->new("PVE::QemuServer::Machine");
182 $qemu_server_machine_module->mock(
183 qemu_machine_pxe => sub {
184 die "invalid test: no runningmachine specified\n"
185 if !defined($vm_status->{runningmachine});
186 return $vm_status->{runningmachine};
187 },
188 );
189
190 my $ssh_info_module = Test::MockModule->new("PVE::SSHInfo");
191 $ssh_info_module->mock(
192 get_ssh_info => sub {
193 my ($node, $network_cidr) = @_;
194 return {
195 ip => '1.2.3.4',
196 name => $node,
197 network => $network_cidr,
198 };
199 },
200 );
201
202 $MigrationTest::Shared::storage_module->mock(
203 storage_migrate => sub {
204 my ($cfg, $volid, $target_sshinfo, $target_storeid, $opts, $logfunc) = @_;
205
206 die "storage_migrate '$volid' error\n"
207 if $fail_config->{storage_migrate} && $fail_config->{storage_migrate} eq $volid;
208
209 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
210
211 die "invalid test: need to add entry for '$volid' to storage_migrate_map\n"
212 if $storeid ne $target_storeid && !defined($storage_migrate_map->{$volid});
213
214 my $target_volname = $storage_migrate_map->{$volid} // $opts->{target_volname} // $volname;
215 my $target_volid = "${target_storeid}:${target_volname}";
216 MigrationTest::Shared::add_target_volid($target_volid);
217
218 return $target_volid;
219 },
220 vdisk_list => sub { # expects vmid to be set
221 my ($cfg, $storeid, $vmid, $vollist) = @_;
222
223 my @storeids = defined($storeid) ? ($storeid) : keys %{$source_vdisks};
224
225 my $res = {};
226 foreach my $storeid (@storeids) {
227 my $list_for_storeid = $source_vdisks->{$storeid};
228 my @list_for_vm = grep { $_->{vmid} eq $vmid } @{$list_for_storeid};
229 $res->{$storeid} = \@list_for_vm;
230 }
231 return $res;
232 },
233 vdisk_free => sub {
234 my ($scfg, $volid) = @_;
235
236 PVE::Storage::parse_volume_id($volid);
237
238 die "vdisk_free '$volid' error\n"
239 if defined($fail_config->{vdisk_free}) && $fail_config->{vdisk_free} eq $volid;
240
241 delete $source_volids->{$volid};
242 },
243 );
244
245 $MigrationTest::Shared::tools_module->mock(
246 get_host_address_family => sub {
247 die "get_host_address_family (mocked) - implement me\n"; # currently no call should lead here
248 },
249 next_migrate_port => sub {
250 die "next_migrate_port (mocked) - implement me\n"; # currently no call should lead here
251 },
252 run_command => sub {
253 my ($cmd_tail, %param) = @_;
254
255 my $cmd_msg = to_json($cmd_tail);
256
257 my $cmd = shift @{$cmd_tail};
258
259 if ($cmd =~ m@^(?:/usr/bin/)?ssh$@) {
260 while (scalar(@{$cmd_tail})) {
261 $cmd = shift @{$cmd_tail};
262 if ($cmd eq '/bin/true') {
263 return 0;
264 } elsif ($cmd eq 'qm') {
265 $cmd = shift @{$cmd_tail};
266 if ($cmd eq 'start') {
267 delete $expected_calls->{ssh_qm_start};
268
269 delete $vm_status->{runningmachine};
270 delete $vm_status->{runningcpu};
271
272 my @options = ( @{$cmd_tail} );
273 while (scalar(@options)) {
274 my $opt = shift @options;
275 if ($opt eq '--machine') {
276 $vm_status->{runningmachine} = shift @options;
277 } elsif ($opt eq '--force-cpu') {
278 $vm_status->{runningcpu} = shift @options;
279 }
280 }
281
282 return $MigrationTest::Shared::tools_module->original('run_command')->(
283 [
284 '/usr/bin/perl',
285 "-I${QM_LIB_PATH}",
286 "-I${QM_LIB_PATH}/test",
287 "${QM_LIB_PATH}/test/MigrationTest/QmMock.pm",
288 'start',
289 @{$cmd_tail},
290 ],
291 %param,
292 );
293
294 } elsif ($cmd eq 'nbdstop') {
295 delete $expected_calls->{ssh_nbdstop};
296 return 0;
297 } elsif ($cmd eq 'resume') {
298 return 0;
299 } elsif ($cmd eq 'unlock') {
300 my $vmid = shift @{$cmd_tail};;
301 die "unlocking wrong vmid: $vmid\n" if $vmid ne $test_vmid;
302 PVE::QemuConfig->remove_lock($vmid);
303 return 0;
304 } elsif ($cmd eq 'stop') {
305 return 0;
306 }
307 die "run_command (mocked) ssh qm command - implement me: ${cmd_msg}";
308 } elsif ($cmd eq 'pvesm') {
309 $cmd = shift @{$cmd_tail};
310 if ($cmd eq 'free') {
311 my $volid = shift @{$cmd_tail};
312 PVE::Storage::parse_volume_id($volid);
313 return 1
314 if $fail_config->{ssh_pvesm_free} && $fail_config->{ssh_pvesm_free} eq $volid;
315 MigrationTest::Shared::remove_target_volid($volid);
316 return 0;
317 }
318 die "run_command (mocked) ssh pvesm command - implement me: ${cmd_msg}";
319 }
320 }
321 die "run_command (mocked) ssh command - implement me: ${cmd_msg}";
322 }
323 die "run_command (mocked) - implement me: ${cmd_msg}";
324 },
325 );
326
327 eval { PVE::QemuMigrate->migrate($test_target, undef, $test_vmid, $test_opts) };
328 my $error = $@;
329
330 file_set_contents("${RUN_DIR_PATH}/source_volids", to_json($source_volids));
331 file_set_contents("${RUN_DIR_PATH}/vm_status", to_json($vm_status));
332 file_set_contents("${RUN_DIR_PATH}/expected_calls", to_json($expected_calls));
333 file_set_contents("${RUN_DIR_PATH}/log", $current_log);
334
335 die $error if $error;
336
337 1;