]>
Commit | Line | Data |
---|---|---|
ffda963f FG |
1 | package PVE::QemuConfig; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
b2c9558d FG |
6 | use PVE::AbstractConfig; |
7 | use PVE::INotify; | |
09253883 | 8 | use PVE::JSONSchema; |
ea1c2110 | 9 | use PVE::QemuServer::CPUConfig; |
a3597c94 | 10 | use PVE::QemuServer::Drive; |
d036e418 | 11 | use PVE::QemuServer::Helpers; |
0a13e08e | 12 | use PVE::QemuServer::Monitor qw(mon_cmd); |
b2c9558d | 13 | use PVE::QemuServer; |
3392d6ca | 14 | use PVE::QemuServer::Machine; |
b2c9558d FG |
15 | use PVE::Storage; |
16 | use PVE::Tools; | |
969eb0b8 | 17 | use PVE::Format qw(render_bytes render_duration); |
b2c9558d | 18 | |
ffda963f FG |
19 | use base qw(PVE::AbstractConfig); |
20 | ||
21 | my $nodename = PVE::INotify::nodename(); | |
22 | ||
23 | mkdir "/etc/pve/nodes/$nodename"; | |
d036e418 | 24 | mkdir "/etc/pve/nodes/$nodename/qemu-server"; |
ffda963f FG |
25 | |
26 | my $lock_dir = "/var/lock/qemu-server"; | |
27 | mkdir $lock_dir; | |
28 | ||
babf613a SR |
29 | sub assert_config_exists_on_node { |
30 | my ($vmid, $node) = @_; | |
31 | ||
32 | $node //= $nodename; | |
33 | ||
34 | my $filename = __PACKAGE__->config_file($vmid, $node); | |
35 | my $exists = -f $filename; | |
36 | ||
37 | my $type = guest_type(); | |
38 | die "unable to find configuration file for $type $vmid on node '$node'\n" | |
39 | if !$exists; | |
40 | } | |
41 | ||
ffda963f FG |
42 | # BEGIN implemented abstract methods from PVE::AbstractConfig |
43 | ||
44 | sub guest_type { | |
45 | return "VM"; | |
46 | } | |
47 | ||
48 | sub __config_max_unused_disks { | |
3aa44d3b | 49 | my ($class) = @_; |
ffda963f | 50 | |
a3597c94 | 51 | return $PVE::QemuServer::Drive::MAX_UNUSED_DISKS; |
ffda963f FG |
52 | } |
53 | ||
54 | sub config_file_lock { | |
55 | my ($class, $vmid) = @_; | |
56 | ||
57 | return "$lock_dir/lock-$vmid.conf"; | |
58 | } | |
59 | ||
60 | sub cfs_config_path { | |
61 | my ($class, $vmid, $node) = @_; | |
62 | ||
63 | $node = $nodename if !$node; | |
64 | return "nodes/$node/qemu-server/$vmid.conf"; | |
65 | } | |
66 | ||
b2c9558d FG |
67 | sub has_feature { |
68 | my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_; | |
69 | ||
70 | my $err; | |
912792e2 | 71 | $class->foreach_volume($conf, sub { |
b2c9558d FG |
72 | my ($ds, $drive) = @_; |
73 | ||
74 | return if PVE::QemuServer::drive_is_cdrom($drive); | |
5282865b | 75 | return if $backup_only && defined($drive->{backup}) && !$drive->{backup}; |
b2c9558d FG |
76 | my $volid = $drive->{file}; |
77 | $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $volid, $snapname, $running); | |
78 | }); | |
79 | ||
80 | return $err ? 0 : 1; | |
81 | } | |
82 | ||
beed0e3b FE |
83 | sub valid_volume_keys { |
84 | my ($class, $reverse) = @_; | |
85 | ||
86 | my @keys = PVE::QemuServer::Drive::valid_drive_names(); | |
87 | ||
88 | return $reverse ? reverse @keys : @keys; | |
89 | } | |
90 | ||
91 | # FIXME: adapt parse_drive to use $noerr for better error messages | |
92 | sub parse_volume { | |
93 | my ($class, $key, $volume_string, $noerr) = @_; | |
94 | ||
09253883 FE |
95 | my $volume; |
96 | if ($key eq 'vmstate') { | |
97 | eval { PVE::JSONSchema::check_format('pve-volume-id', $volume_string) }; | |
98 | if (my $err = $@) { | |
d1c1af4b | 99 | return if $noerr; |
09253883 FE |
100 | die $err; |
101 | } | |
102 | $volume = { 'file' => $volume_string }; | |
103 | } else { | |
104 | $volume = PVE::QemuServer::Drive::parse_drive($key, $volume_string); | |
105 | } | |
beed0e3b FE |
106 | |
107 | die "unable to parse volume\n" if !defined($volume) && !$noerr; | |
108 | ||
109 | return $volume; | |
110 | } | |
111 | ||
112 | sub print_volume { | |
113 | my ($class, $key, $volume) = @_; | |
114 | ||
115 | return PVE::QemuServer::Drive::print_drive($volume); | |
116 | } | |
117 | ||
118 | sub volid_key { | |
119 | my ($class) = @_; | |
120 | ||
121 | return 'file'; | |
122 | } | |
123 | ||
3aa44d3b | 124 | sub get_replicatable_volumes { |
c78f43b9 | 125 | my ($class, $storecfg, $vmid, $conf, $cleanup, $noerr) = @_; |
3aa44d3b DM |
126 | |
127 | my $volhash = {}; | |
128 | ||
129 | my $test_volid = sub { | |
f949eb77 | 130 | my ($volid, $attr) = @_; |
3aa44d3b | 131 | |
a6cb40f7 DM |
132 | return if $attr->{cdrom}; |
133 | ||
134 | return if !$cleanup && !$attr->{replicate}; | |
135 | ||
4ab3bcc8 DM |
136 | if ($volid =~ m|^/|) { |
137 | return if !$attr->{replicate}; | |
a6cb40f7 | 138 | return if $cleanup || $noerr; |
4ab3bcc8 DM |
139 | die "unable to replicate local file/device '$volid'\n"; |
140 | } | |
141 | ||
a722d4ff DM |
142 | my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, $noerr); |
143 | return if !$storeid; | |
144 | ||
6f249d94 | 145 | my $scfg = PVE::Storage::storage_config($storecfg, $storeid); |
a722d4ff DM |
146 | return if $scfg->{shared}; |
147 | ||
f7e7767f DM |
148 | my ($path, $owner, $vtype) = PVE::Storage::path($storecfg, $volid); |
149 | return if !$owner || ($owner != $vmid); | |
150 | ||
a6cb40f7 DM |
151 | if ($vtype ne 'images') { |
152 | return if $cleanup || $noerr; | |
153 | die "unable to replicate volume '$volid', type '$vtype'\n"; | |
154 | } | |
3aa44d3b DM |
155 | |
156 | if (!PVE::Storage::volume_has_feature($storecfg, 'replicate', $volid)) { | |
e5857ca8 | 157 | return if $cleanup || $noerr; |
3aa44d3b DM |
158 | die "missing replicate feature on volume '$volid'\n"; |
159 | } | |
160 | ||
161 | $volhash->{$volid} = 1; | |
162 | }; | |
163 | ||
f949eb77 | 164 | PVE::QemuServer::foreach_volid($conf, $test_volid); |
3aa44d3b DM |
165 | |
166 | return $volhash; | |
167 | } | |
168 | ||
185df962 AL |
169 | sub get_backup_volumes { |
170 | my ($class, $conf) = @_; | |
171 | ||
172 | my $return_volumes = []; | |
173 | ||
174 | my $test_volume = sub { | |
175 | my ($key, $drive) = @_; | |
176 | ||
177 | return if PVE::QemuServer::drive_is_cdrom($drive); | |
178 | ||
179 | my $included = $drive->{backup} // 1; | |
180 | my $reason = "backup="; | |
181 | $reason .= defined($drive->{backup}) ? 'no' : 'yes'; | |
182 | ||
183 | if ($key =~ m/^efidisk/ && (!defined($conf->{bios}) || $conf->{bios} ne 'ovmf')) { | |
184 | $included = 0; | |
185 | $reason = "efidisk but no OMVF BIOS"; | |
186 | } | |
187 | ||
188 | push @$return_volumes, { | |
189 | key => $key, | |
190 | included => $included, | |
191 | reason => $reason, | |
192 | volume_config => $drive, | |
193 | }; | |
194 | }; | |
195 | ||
196 | PVE::QemuConfig->foreach_volume($conf, $test_volume); | |
197 | ||
198 | return $return_volumes; | |
199 | } | |
200 | ||
b2c9558d | 201 | sub __snapshot_save_vmstate { |
48b4cdc2 | 202 | my ($class, $vmid, $conf, $snapname, $storecfg, $statestorage, $suspend) = @_; |
b2c9558d | 203 | |
66cebc46 DC |
204 | # use given storage or search for one from the config |
205 | my $target = $statestorage; | |
b2c9558d FG |
206 | |
207 | if (!$target) { | |
66cebc46 | 208 | $target = PVE::QemuServer::find_vmstate_storage($conf, $storecfg); |
2eeb0c93 | 209 | } |
b2c9558d | 210 | |
22ea69ca AA |
211 | my $defaults = PVE::QemuServer::load_defaults(); |
212 | my $mem_size = $conf->{memory} // $defaults->{memory}; | |
566caaa4 | 213 | my $driver_state_size = 500; # assume 500MB is enough to safe all driver state; |
b3983fa1 DC |
214 | # our savevm-start does live-save of the memory until the space left in the |
215 | # volume is just enough for the remaining memory content + internal state | |
216 | # then it stops the vm and copies the rest so we reserve twice the | |
217 | # memory content + state to minimize vm downtime | |
22ea69ca | 218 | my $size = $mem_size*2 + $driver_state_size; |
159719e5 | 219 | my $scfg = PVE::Storage::storage_config($storecfg, $target); |
b2c9558d FG |
220 | |
221 | my $name = "vm-$vmid-state-$snapname"; | |
b2c9558d | 222 | $name .= ".raw" if $scfg->{path}; # add filename extension for file base storage |
159719e5 DC |
223 | |
224 | my $statefile = PVE::Storage::vdisk_alloc($storecfg, $target, $vmid, 'raw', $name, $size*1024); | |
3392d6ca | 225 | my $runningmachine = PVE::QemuServer::Machine::get_current_qemu_machine($vmid); |
159719e5 | 226 | |
ea1c2110 SR |
227 | # get current QEMU -cpu argument to ensure consistency of custom CPU models |
228 | my $runningcpu; | |
229 | if (my $pid = PVE::QemuServer::check_running($vmid)) { | |
230 | $runningcpu = PVE::QemuServer::CPUConfig::get_cpu_from_running_vm($pid); | |
231 | } | |
232 | ||
233 | if (!$suspend) { | |
234 | $conf = $conf->{snapshots}->{$snapname}; | |
159719e5 DC |
235 | } |
236 | ||
ea1c2110 SR |
237 | $conf->{vmstate} = $statefile; |
238 | $conf->{runningmachine} = $runningmachine; | |
239 | $conf->{runningcpu} = $runningcpu; | |
240 | ||
159719e5 | 241 | return $statefile; |
b2c9558d FG |
242 | } |
243 | ||
244 | sub __snapshot_check_running { | |
245 | my ($class, $vmid) = @_; | |
babf613a | 246 | return PVE::QemuServer::Helpers::vm_running_locally($vmid); |
b2c9558d FG |
247 | } |
248 | ||
249 | sub __snapshot_check_freeze_needed { | |
250 | my ($class, $vmid, $config, $save_vmstate) = @_; | |
251 | ||
252 | my $running = $class->__snapshot_check_running($vmid); | |
278e2c9d | 253 | if (!$save_vmstate) { |
9d66b397 | 254 | return ($running, $running && PVE::QemuServer::parse_guest_agent($config)->{enabled} && PVE::QemuServer::qga_check_running($vmid)); |
b2c9558d FG |
255 | } else { |
256 | return ($running, 0); | |
257 | } | |
258 | } | |
259 | ||
260 | sub __snapshot_freeze { | |
261 | my ($class, $vmid, $unfreeze) = @_; | |
262 | ||
263 | if ($unfreeze) { | |
0a13e08e | 264 | eval { mon_cmd($vmid, "guest-fsfreeze-thaw"); }; |
b2c9558d FG |
265 | warn "guest-fsfreeze-thaw problems - $@" if $@; |
266 | } else { | |
0a13e08e | 267 | eval { mon_cmd($vmid, "guest-fsfreeze-freeze"); }; |
b2c9558d FG |
268 | warn "guest-fsfreeze-freeze problems - $@" if $@; |
269 | } | |
270 | } | |
271 | ||
272 | sub __snapshot_create_vol_snapshots_hook { | |
273 | my ($class, $vmid, $snap, $running, $hook) = @_; | |
274 | ||
275 | if ($running) { | |
3a8deb55 AD |
276 | my $storecfg = PVE::Storage::config(); |
277 | ||
b2c9558d FG |
278 | if ($hook eq "before") { |
279 | if ($snap->{vmstate}) { | |
b2c9558d | 280 | my $path = PVE::Storage::path($storecfg, $snap->{vmstate}); |
3a8deb55 | 281 | PVE::Storage::activate_volumes($storecfg, [$snap->{vmstate}]); |
f97224b1 | 282 | my $state_storage_id = PVE::Storage::parse_volume_id($snap->{vmstate}); |
3a8deb55 | 283 | |
27a5be53 | 284 | PVE::QemuServer::set_migration_caps($vmid, 1); |
0a13e08e | 285 | mon_cmd($vmid, "savevm-start", statefile => $path); |
f97224b1 | 286 | print "saving VM state and RAM using storage '$state_storage_id'\n"; |
969eb0b8 SR |
287 | my $render_state = sub { |
288 | my ($stat) = @_; | |
289 | my $b = render_bytes($stat->{bytes}); | |
290 | my $t = render_duration($stat->{'total-time'} / 1000); | |
291 | return ($b, $t); | |
292 | }; | |
98308873 | 293 | my $round = 0; |
b2c9558d | 294 | for(;;) { |
98308873 | 295 | $round++; |
0a13e08e | 296 | my $stat = mon_cmd($vmid, "query-savevm"); |
b2c9558d FG |
297 | if (!$stat->{status}) { |
298 | die "savevm not active\n"; | |
299 | } elsif ($stat->{status} eq 'active') { | |
98308873 TL |
300 | if ($round < 60 || $round % 10 == 0) { |
301 | my ($b, $t) = $render_state->($stat); | |
302 | print "$b in $t\n"; | |
303 | } | |
304 | print "reducing reporting rate to every 10s\n" if $round == 60; | |
b2c9558d FG |
305 | sleep(1); |
306 | next; | |
307 | } elsif ($stat->{status} eq 'completed') { | |
969eb0b8 | 308 | my ($b, $t) = $render_state->($stat); |
5c3f7825 | 309 | print "completed saving the VM state in $t, saved $b\n"; |
b2c9558d FG |
310 | last; |
311 | } else { | |
5c3f7825 | 312 | die "query-savevm returned unexpected status '$stat->{status}'\n"; |
b2c9558d FG |
313 | } |
314 | } | |
315 | } else { | |
0a13e08e | 316 | mon_cmd($vmid, "savevm-start"); |
b2c9558d FG |
317 | } |
318 | } elsif ($hook eq "after") { | |
a022e3fd | 319 | eval { |
0a13e08e | 320 | mon_cmd($vmid, "savevm-end"); |
3a8deb55 AD |
321 | PVE::Storage::deactivate_volumes($storecfg, [$snap->{vmstate}]) if $snap->{vmstate}; |
322 | }; | |
b2c9558d FG |
323 | warn $@ if $@; |
324 | } elsif ($hook eq "after-freeze") { | |
325 | # savevm-end is async, we need to wait | |
326 | for (;;) { | |
0a13e08e | 327 | my $stat = mon_cmd($vmid, "query-savevm"); |
b2c9558d FG |
328 | if (!$stat->{bytes}) { |
329 | last; | |
330 | } else { | |
331 | print "savevm not yet finished\n"; | |
332 | sleep(1); | |
333 | next; | |
334 | } | |
335 | } | |
336 | } | |
337 | } | |
338 | } | |
339 | ||
340 | sub __snapshot_create_vol_snapshot { | |
341 | my ($class, $vmid, $ds, $drive, $snapname) = @_; | |
342 | ||
343 | return if PVE::QemuServer::drive_is_cdrom($drive); | |
344 | ||
345 | my $volid = $drive->{file}; | |
346 | my $device = "drive-$ds"; | |
347 | my $storecfg = PVE::Storage::config(); | |
348 | ||
8828460b SR |
349 | print "snapshotting '$device' ($drive->{file})\n"; |
350 | ||
b2c9558d FG |
351 | PVE::QemuServer::qemu_volume_snapshot($vmid, $device, $storecfg, $volid, $snapname); |
352 | } | |
353 | ||
354 | sub __snapshot_delete_remove_drive { | |
355 | my ($class, $snap, $remove_drive) = @_; | |
356 | ||
357 | if ($remove_drive eq 'vmstate') { | |
358 | delete $snap->{$remove_drive}; | |
359 | } else { | |
360 | my $drive = PVE::QemuServer::parse_drive($remove_drive, $snap->{$remove_drive}); | |
361 | return if PVE::QemuServer::drive_is_cdrom($drive); | |
362 | ||
363 | my $volid = $drive->{file}; | |
364 | delete $snap->{$remove_drive}; | |
365 | $class->add_unused_volume($snap, $volid); | |
366 | } | |
367 | } | |
368 | ||
369 | sub __snapshot_delete_vmstate_file { | |
370 | my ($class, $snap, $force) = @_; | |
371 | ||
372 | my $storecfg = PVE::Storage::config(); | |
373 | ||
374 | eval { PVE::Storage::vdisk_free($storecfg, $snap->{vmstate}); }; | |
375 | if (my $err = $@) { | |
376 | die $err if !$force; | |
377 | warn $err; | |
378 | } | |
379 | } | |
380 | ||
381 | sub __snapshot_delete_vol_snapshot { | |
382 | my ($class, $vmid, $ds, $drive, $snapname, $unused) = @_; | |
383 | ||
384 | return if PVE::QemuServer::drive_is_cdrom($drive); | |
385 | my $storecfg = PVE::Storage::config(); | |
386 | my $volid = $drive->{file}; | |
387 | my $device = "drive-$ds"; | |
388 | ||
389 | PVE::QemuServer::qemu_volume_snapshot_delete($vmid, $device, $storecfg, $volid, $snapname); | |
390 | ||
391 | push @$unused, $volid; | |
392 | } | |
393 | ||
58b1a8d7 DC |
394 | sub __snapshot_rollback_hook { |
395 | my ($class, $vmid, $conf, $snap, $prepare, $data) = @_; | |
396 | ||
397 | if ($prepare) { | |
398 | # we save the machine of the current config | |
399 | $data->{oldmachine} = $conf->{machine}; | |
400 | } else { | |
e6d35c71 TL |
401 | # if we have a 'runningmachine' entry in the snapshot we use that |
402 | # for the forcemachine parameter, else we use the old logic | |
c6737ef1 DC |
403 | if (defined($conf->{runningmachine})) { |
404 | $data->{forcemachine} = $conf->{runningmachine}; | |
405 | delete $conf->{runningmachine}; | |
ea1c2110 SR |
406 | |
407 | # runningcpu is newer than runningmachine, so assume it only exists | |
408 | # here, if at all | |
409 | $data->{forcecpu} = delete $conf->{runningcpu} | |
410 | if defined($conf->{runningcpu}); | |
c6737ef1 DC |
411 | } else { |
412 | # Note: old code did not store 'machine', so we try to be smart | |
413 | # and guess the snapshot was generated with kvm 1.4 (pc-i440fx-1.4). | |
414 | $data->{forcemachine} = $conf->{machine} || 'pc-i440fx-1.4'; | |
415 | ||
416 | # we remove the 'machine' configuration if not explicitly specified | |
417 | # in the original config. | |
418 | delete $conf->{machine} if $snap->{vmstate} && !defined($data->{oldmachine}); | |
419 | } | |
6ee499ff DC |
420 | |
421 | if ($conf->{vmgenid}) { | |
4f4d9772 TL |
422 | # tell the VM that it's another generation, so it can react |
423 | # appropriately, e.g. dirty-mark copies of distributed databases or | |
424 | # re-initializing its random number generator | |
6ee499ff DC |
425 | $conf->{vmgenid} = PVE::QemuServer::generate_uuid(); |
426 | } | |
58b1a8d7 DC |
427 | } |
428 | ||
429 | return; | |
430 | } | |
431 | ||
b2c9558d FG |
432 | sub __snapshot_rollback_vol_possible { |
433 | my ($class, $drive, $snapname) = @_; | |
434 | ||
435 | return if PVE::QemuServer::drive_is_cdrom($drive); | |
436 | ||
437 | my $storecfg = PVE::Storage::config(); | |
438 | my $volid = $drive->{file}; | |
439 | ||
440 | PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname); | |
441 | } | |
442 | ||
443 | sub __snapshot_rollback_vol_rollback { | |
444 | my ($class, $drive, $snapname) = @_; | |
445 | ||
446 | return if PVE::QemuServer::drive_is_cdrom($drive); | |
447 | ||
448 | my $storecfg = PVE::Storage::config(); | |
449 | PVE::Storage::volume_snapshot_rollback($storecfg, $drive->{file}, $snapname); | |
450 | } | |
451 | ||
452 | sub __snapshot_rollback_vm_stop { | |
453 | my ($class, $vmid) = @_; | |
454 | ||
455 | my $storecfg = PVE::Storage::config(); | |
456 | PVE::QemuServer::vm_stop($storecfg, $vmid, undef, undef, 5, undef, undef); | |
457 | } | |
458 | ||
459 | sub __snapshot_rollback_vm_start { | |
58b1a8d7 | 460 | my ($class, $vmid, $vmstate, $data) = @_; |
b2c9558d FG |
461 | |
462 | my $storecfg = PVE::Storage::config(); | |
0c498cca FG |
463 | my $params = { |
464 | statefile => $vmstate, | |
465 | forcemachine => $data->{forcemachine}, | |
ea1c2110 | 466 | forcecpu => $data->{forcecpu}, |
0c498cca FG |
467 | }; |
468 | PVE::QemuServer::vm_start($storecfg, $vmid, $params); | |
b2c9558d FG |
469 | } |
470 | ||
c4a54ed5 FG |
471 | sub __snapshot_rollback_get_unused { |
472 | my ($class, $conf, $snap) = @_; | |
473 | ||
474 | my $unused = []; | |
475 | ||
beed0e3b | 476 | $class->foreach_volume($conf, sub { |
c4a54ed5 FG |
477 | my ($vs, $volume) = @_; |
478 | ||
479 | return if PVE::QemuServer::drive_is_cdrom($volume); | |
480 | ||
481 | my $found = 0; | |
482 | my $volid = $volume->{file}; | |
483 | ||
beed0e3b | 484 | $class->foreach_volume($snap, sub { |
c4a54ed5 FG |
485 | my ($ds, $drive) = @_; |
486 | ||
487 | return if $found; | |
488 | return if PVE::QemuServer::drive_is_cdrom($drive); | |
489 | ||
490 | $found = 1 | |
491 | if ($drive->{file} && $drive->{file} eq $volid); | |
492 | }); | |
493 | ||
494 | push @$unused, $volid if !$found; | |
495 | }); | |
496 | ||
497 | return $unused; | |
498 | } | |
499 | ||
ffda963f FG |
500 | # END implemented abstract methods from PVE::AbstractConfig |
501 | ||
502 | 1; |