]>
Commit | Line | Data |
---|---|---|
48831384 FE |
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 | ||
e594231b FG |
54 | my $tunnel_module = Test::MockModule->new("PVE::Tunnel"); |
55 | $tunnel_module->mock( | |
48831384 FE |
56 | finish_tunnel => sub { |
57 | delete $expected_calls->{'finish_tunnel'}; | |
58 | return; | |
59 | }, | |
e594231b FG |
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( | |
48831384 FE |
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 | }, | |
48831384 FE |
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; | |
cd1db1b3 TL |
136 | die "qemu_drive_mirror '$drive' error\n" |
137 | if $fail_config->{qemu_drive_mirror} && $fail_config->{qemu_drive_mirror} eq $drive; | |
48831384 FE |
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 { | |
0783c3c2 FE |
146 | my ($vmid, $vmiddst, $jobs, $completion, $qga) = @_; |
147 | ||
cd1db1b3 TL |
148 | if ($fail_config->{qemu_drive_mirror_monitor} |
149 | && $fail_config->{qemu_drive_mirror_monitor} eq $completion | |
150 | ) { | |
0783c3c2 FE |
151 | die "qemu_drive_mirror_monitor '$completion' error\n"; |
152 | } | |
48831384 FE |
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 | }, | |
73ed6496 | 162 | del_nets_bridge_fdb => sub { return; }, |
48831384 FE |
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 | ||
cd1db1b3 TL |
206 | die "storage_migrate '$volid' error\n" |
207 | if $fail_config->{storage_migrate} && $fail_config->{storage_migrate} eq $volid; | |
48831384 FE |
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 | ||
93a1c63f FE |
236 | PVE::Storage::parse_volume_id($volid); |
237 | ||
cd1db1b3 TL |
238 | die "vdisk_free '$volid' error\n" |
239 | if defined($fail_config->{vdisk_free}) && $fail_config->{vdisk_free} eq $volid; | |
48831384 FE |
240 | |
241 | delete $source_volids->{$volid}; | |
242 | }, | |
a0dbed5a AL |
243 | volume_size_info => sub { |
244 | my ($scfg, $volid) = @_; | |
245 | my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid); | |
246 | ||
247 | for my $v ($source_vdisks->{$storeid}->@*) { | |
248 | return wantarray ? ($v->{size}, $v->{format}, $v->{used}, $v->{parent}) : $v->{size} | |
249 | if $v->{volid} eq $volid; | |
250 | } | |
251 | die "could not find '$volid' in mock 'source_vdisks'\n"; | |
252 | }, | |
48831384 FE |
253 | ); |
254 | ||
255 | $MigrationTest::Shared::tools_module->mock( | |
256 | get_host_address_family => sub { | |
257 | die "get_host_address_family (mocked) - implement me\n"; # currently no call should lead here | |
258 | }, | |
259 | next_migrate_port => sub { | |
260 | die "next_migrate_port (mocked) - implement me\n"; # currently no call should lead here | |
261 | }, | |
262 | run_command => sub { | |
263 | my ($cmd_tail, %param) = @_; | |
264 | ||
265 | my $cmd_msg = to_json($cmd_tail); | |
266 | ||
267 | my $cmd = shift @{$cmd_tail}; | |
268 | ||
cd1db1b3 | 269 | if ($cmd =~ m@^(?:/usr/bin/)?ssh$@) { |
48831384 FE |
270 | while (scalar(@{$cmd_tail})) { |
271 | $cmd = shift @{$cmd_tail}; | |
272 | if ($cmd eq '/bin/true') { | |
273 | return 0; | |
274 | } elsif ($cmd eq 'qm') { | |
275 | $cmd = shift @{$cmd_tail}; | |
276 | if ($cmd eq 'start') { | |
277 | delete $expected_calls->{ssh_qm_start}; | |
278 | ||
279 | delete $vm_status->{runningmachine}; | |
280 | delete $vm_status->{runningcpu}; | |
281 | ||
282 | my @options = ( @{$cmd_tail} ); | |
283 | while (scalar(@options)) { | |
284 | my $opt = shift @options; | |
285 | if ($opt eq '--machine') { | |
286 | $vm_status->{runningmachine} = shift @options; | |
287 | } elsif ($opt eq '--force-cpu') { | |
288 | $vm_status->{runningcpu} = shift @options; | |
289 | } | |
290 | } | |
291 | ||
cd1db1b3 TL |
292 | return $MigrationTest::Shared::tools_module->original('run_command')->( |
293 | [ | |
294 | '/usr/bin/perl', | |
295 | "-I${QM_LIB_PATH}", | |
296 | "-I${QM_LIB_PATH}/test", | |
297 | "${QM_LIB_PATH}/test/MigrationTest/QmMock.pm", | |
298 | 'start', | |
299 | @{$cmd_tail}, | |
300 | ], | |
301 | %param, | |
302 | ); | |
48831384 FE |
303 | |
304 | } elsif ($cmd eq 'nbdstop') { | |
305 | delete $expected_calls->{ssh_nbdstop}; | |
306 | return 0; | |
307 | } elsif ($cmd eq 'resume') { | |
308 | return 0; | |
309 | } elsif ($cmd eq 'unlock') { | |
310 | my $vmid = shift @{$cmd_tail};; | |
311 | die "unlocking wrong vmid: $vmid\n" if $vmid ne $test_vmid; | |
312 | PVE::QemuConfig->remove_lock($vmid); | |
313 | return 0; | |
314 | } elsif ($cmd eq 'stop') { | |
315 | return 0; | |
316 | } | |
317 | die "run_command (mocked) ssh qm command - implement me: ${cmd_msg}"; | |
318 | } elsif ($cmd eq 'pvesm') { | |
319 | $cmd = shift @{$cmd_tail}; | |
320 | if ($cmd eq 'free') { | |
321 | my $volid = shift @{$cmd_tail}; | |
93a1c63f | 322 | PVE::Storage::parse_volume_id($volid); |
cd1db1b3 TL |
323 | return 1 |
324 | if $fail_config->{ssh_pvesm_free} && $fail_config->{ssh_pvesm_free} eq $volid; | |
48831384 FE |
325 | MigrationTest::Shared::remove_target_volid($volid); |
326 | return 0; | |
327 | } | |
328 | die "run_command (mocked) ssh pvesm command - implement me: ${cmd_msg}"; | |
329 | } | |
330 | } | |
331 | die "run_command (mocked) ssh command - implement me: ${cmd_msg}"; | |
332 | } | |
333 | die "run_command (mocked) - implement me: ${cmd_msg}"; | |
334 | }, | |
335 | ); | |
336 | ||
337 | eval { PVE::QemuMigrate->migrate($test_target, undef, $test_vmid, $test_opts) }; | |
338 | my $error = $@; | |
339 | ||
340 | file_set_contents("${RUN_DIR_PATH}/source_volids", to_json($source_volids)); | |
341 | file_set_contents("${RUN_DIR_PATH}/vm_status", to_json($vm_status)); | |
342 | file_set_contents("${RUN_DIR_PATH}/expected_calls", to_json($expected_calls)); | |
343 | file_set_contents("${RUN_DIR_PATH}/log", $current_log); | |
344 | ||
345 | die $error if $error; | |
346 | ||
347 | 1; |