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