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