]>
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 = $@) { | |
98 | return undef if $noerr; | |
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 | ||
b2c9558d | 168 | sub __snapshot_save_vmstate { |
48b4cdc2 | 169 | my ($class, $vmid, $conf, $snapname, $storecfg, $statestorage, $suspend) = @_; |
b2c9558d | 170 | |
66cebc46 DC |
171 | # use given storage or search for one from the config |
172 | my $target = $statestorage; | |
b2c9558d FG |
173 | |
174 | if (!$target) { | |
66cebc46 | 175 | $target = PVE::QemuServer::find_vmstate_storage($conf, $storecfg); |
2eeb0c93 | 176 | } |
b2c9558d | 177 | |
22ea69ca AA |
178 | my $defaults = PVE::QemuServer::load_defaults(); |
179 | my $mem_size = $conf->{memory} // $defaults->{memory}; | |
566caaa4 | 180 | my $driver_state_size = 500; # assume 500MB is enough to safe all driver state; |
b3983fa1 DC |
181 | # our savevm-start does live-save of the memory until the space left in the |
182 | # volume is just enough for the remaining memory content + internal state | |
183 | # then it stops the vm and copies the rest so we reserve twice the | |
184 | # memory content + state to minimize vm downtime | |
22ea69ca | 185 | my $size = $mem_size*2 + $driver_state_size; |
159719e5 | 186 | my $scfg = PVE::Storage::storage_config($storecfg, $target); |
b2c9558d FG |
187 | |
188 | my $name = "vm-$vmid-state-$snapname"; | |
b2c9558d | 189 | $name .= ".raw" if $scfg->{path}; # add filename extension for file base storage |
159719e5 DC |
190 | |
191 | my $statefile = PVE::Storage::vdisk_alloc($storecfg, $target, $vmid, 'raw', $name, $size*1024); | |
3392d6ca | 192 | my $runningmachine = PVE::QemuServer::Machine::get_current_qemu_machine($vmid); |
159719e5 | 193 | |
ea1c2110 SR |
194 | # get current QEMU -cpu argument to ensure consistency of custom CPU models |
195 | my $runningcpu; | |
196 | if (my $pid = PVE::QemuServer::check_running($vmid)) { | |
197 | $runningcpu = PVE::QemuServer::CPUConfig::get_cpu_from_running_vm($pid); | |
198 | } | |
199 | ||
200 | if (!$suspend) { | |
201 | $conf = $conf->{snapshots}->{$snapname}; | |
159719e5 DC |
202 | } |
203 | ||
ea1c2110 SR |
204 | $conf->{vmstate} = $statefile; |
205 | $conf->{runningmachine} = $runningmachine; | |
206 | $conf->{runningcpu} = $runningcpu; | |
207 | ||
159719e5 | 208 | return $statefile; |
b2c9558d FG |
209 | } |
210 | ||
211 | sub __snapshot_check_running { | |
212 | my ($class, $vmid) = @_; | |
babf613a | 213 | return PVE::QemuServer::Helpers::vm_running_locally($vmid); |
b2c9558d FG |
214 | } |
215 | ||
216 | sub __snapshot_check_freeze_needed { | |
217 | my ($class, $vmid, $config, $save_vmstate) = @_; | |
218 | ||
219 | my $running = $class->__snapshot_check_running($vmid); | |
278e2c9d | 220 | if (!$save_vmstate) { |
9d66b397 | 221 | return ($running, $running && PVE::QemuServer::parse_guest_agent($config)->{enabled} && PVE::QemuServer::qga_check_running($vmid)); |
b2c9558d FG |
222 | } else { |
223 | return ($running, 0); | |
224 | } | |
225 | } | |
226 | ||
227 | sub __snapshot_freeze { | |
228 | my ($class, $vmid, $unfreeze) = @_; | |
229 | ||
230 | if ($unfreeze) { | |
0a13e08e | 231 | eval { mon_cmd($vmid, "guest-fsfreeze-thaw"); }; |
b2c9558d FG |
232 | warn "guest-fsfreeze-thaw problems - $@" if $@; |
233 | } else { | |
0a13e08e | 234 | eval { mon_cmd($vmid, "guest-fsfreeze-freeze"); }; |
b2c9558d FG |
235 | warn "guest-fsfreeze-freeze problems - $@" if $@; |
236 | } | |
237 | } | |
238 | ||
239 | sub __snapshot_create_vol_snapshots_hook { | |
240 | my ($class, $vmid, $snap, $running, $hook) = @_; | |
241 | ||
242 | if ($running) { | |
3a8deb55 AD |
243 | my $storecfg = PVE::Storage::config(); |
244 | ||
b2c9558d FG |
245 | if ($hook eq "before") { |
246 | if ($snap->{vmstate}) { | |
b2c9558d | 247 | my $path = PVE::Storage::path($storecfg, $snap->{vmstate}); |
3a8deb55 AD |
248 | PVE::Storage::activate_volumes($storecfg, [$snap->{vmstate}]); |
249 | ||
0a13e08e | 250 | mon_cmd($vmid, "savevm-start", statefile => $path); |
b2c9558d | 251 | for(;;) { |
0a13e08e | 252 | my $stat = mon_cmd($vmid, "query-savevm"); |
b2c9558d FG |
253 | if (!$stat->{status}) { |
254 | die "savevm not active\n"; | |
255 | } elsif ($stat->{status} eq 'active') { | |
256 | sleep(1); | |
257 | next; | |
258 | } elsif ($stat->{status} eq 'completed') { | |
259 | last; | |
260 | } else { | |
261 | die "query-savevm returned status '$stat->{status}'\n"; | |
262 | } | |
263 | } | |
264 | } else { | |
0a13e08e | 265 | mon_cmd($vmid, "savevm-start"); |
b2c9558d FG |
266 | } |
267 | } elsif ($hook eq "after") { | |
a022e3fd | 268 | eval { |
0a13e08e | 269 | mon_cmd($vmid, "savevm-end"); |
3a8deb55 AD |
270 | PVE::Storage::deactivate_volumes($storecfg, [$snap->{vmstate}]) if $snap->{vmstate}; |
271 | }; | |
b2c9558d FG |
272 | warn $@ if $@; |
273 | } elsif ($hook eq "after-freeze") { | |
274 | # savevm-end is async, we need to wait | |
275 | for (;;) { | |
0a13e08e | 276 | my $stat = mon_cmd($vmid, "query-savevm"); |
b2c9558d FG |
277 | if (!$stat->{bytes}) { |
278 | last; | |
279 | } else { | |
280 | print "savevm not yet finished\n"; | |
281 | sleep(1); | |
282 | next; | |
283 | } | |
284 | } | |
285 | } | |
286 | } | |
287 | } | |
288 | ||
289 | sub __snapshot_create_vol_snapshot { | |
290 | my ($class, $vmid, $ds, $drive, $snapname) = @_; | |
291 | ||
292 | return if PVE::QemuServer::drive_is_cdrom($drive); | |
293 | ||
294 | my $volid = $drive->{file}; | |
295 | my $device = "drive-$ds"; | |
296 | my $storecfg = PVE::Storage::config(); | |
297 | ||
298 | PVE::QemuServer::qemu_volume_snapshot($vmid, $device, $storecfg, $volid, $snapname); | |
299 | } | |
300 | ||
301 | sub __snapshot_delete_remove_drive { | |
302 | my ($class, $snap, $remove_drive) = @_; | |
303 | ||
304 | if ($remove_drive eq 'vmstate') { | |
305 | delete $snap->{$remove_drive}; | |
306 | } else { | |
307 | my $drive = PVE::QemuServer::parse_drive($remove_drive, $snap->{$remove_drive}); | |
308 | return if PVE::QemuServer::drive_is_cdrom($drive); | |
309 | ||
310 | my $volid = $drive->{file}; | |
311 | delete $snap->{$remove_drive}; | |
312 | $class->add_unused_volume($snap, $volid); | |
313 | } | |
314 | } | |
315 | ||
316 | sub __snapshot_delete_vmstate_file { | |
317 | my ($class, $snap, $force) = @_; | |
318 | ||
319 | my $storecfg = PVE::Storage::config(); | |
320 | ||
321 | eval { PVE::Storage::vdisk_free($storecfg, $snap->{vmstate}); }; | |
322 | if (my $err = $@) { | |
323 | die $err if !$force; | |
324 | warn $err; | |
325 | } | |
326 | } | |
327 | ||
328 | sub __snapshot_delete_vol_snapshot { | |
329 | my ($class, $vmid, $ds, $drive, $snapname, $unused) = @_; | |
330 | ||
331 | return if PVE::QemuServer::drive_is_cdrom($drive); | |
332 | my $storecfg = PVE::Storage::config(); | |
333 | my $volid = $drive->{file}; | |
334 | my $device = "drive-$ds"; | |
335 | ||
336 | PVE::QemuServer::qemu_volume_snapshot_delete($vmid, $device, $storecfg, $volid, $snapname); | |
337 | ||
338 | push @$unused, $volid; | |
339 | } | |
340 | ||
58b1a8d7 DC |
341 | sub __snapshot_rollback_hook { |
342 | my ($class, $vmid, $conf, $snap, $prepare, $data) = @_; | |
343 | ||
344 | if ($prepare) { | |
345 | # we save the machine of the current config | |
346 | $data->{oldmachine} = $conf->{machine}; | |
347 | } else { | |
e6d35c71 TL |
348 | # if we have a 'runningmachine' entry in the snapshot we use that |
349 | # for the forcemachine parameter, else we use the old logic | |
c6737ef1 DC |
350 | if (defined($conf->{runningmachine})) { |
351 | $data->{forcemachine} = $conf->{runningmachine}; | |
352 | delete $conf->{runningmachine}; | |
ea1c2110 SR |
353 | |
354 | # runningcpu is newer than runningmachine, so assume it only exists | |
355 | # here, if at all | |
356 | $data->{forcecpu} = delete $conf->{runningcpu} | |
357 | if defined($conf->{runningcpu}); | |
c6737ef1 DC |
358 | } else { |
359 | # Note: old code did not store 'machine', so we try to be smart | |
360 | # and guess the snapshot was generated with kvm 1.4 (pc-i440fx-1.4). | |
361 | $data->{forcemachine} = $conf->{machine} || 'pc-i440fx-1.4'; | |
362 | ||
363 | # we remove the 'machine' configuration if not explicitly specified | |
364 | # in the original config. | |
365 | delete $conf->{machine} if $snap->{vmstate} && !defined($data->{oldmachine}); | |
366 | } | |
6ee499ff DC |
367 | |
368 | if ($conf->{vmgenid}) { | |
4f4d9772 TL |
369 | # tell the VM that it's another generation, so it can react |
370 | # appropriately, e.g. dirty-mark copies of distributed databases or | |
371 | # re-initializing its random number generator | |
6ee499ff DC |
372 | $conf->{vmgenid} = PVE::QemuServer::generate_uuid(); |
373 | } | |
58b1a8d7 DC |
374 | } |
375 | ||
376 | return; | |
377 | } | |
378 | ||
b2c9558d FG |
379 | sub __snapshot_rollback_vol_possible { |
380 | my ($class, $drive, $snapname) = @_; | |
381 | ||
382 | return if PVE::QemuServer::drive_is_cdrom($drive); | |
383 | ||
384 | my $storecfg = PVE::Storage::config(); | |
385 | my $volid = $drive->{file}; | |
386 | ||
387 | PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname); | |
388 | } | |
389 | ||
390 | sub __snapshot_rollback_vol_rollback { | |
391 | my ($class, $drive, $snapname) = @_; | |
392 | ||
393 | return if PVE::QemuServer::drive_is_cdrom($drive); | |
394 | ||
395 | my $storecfg = PVE::Storage::config(); | |
396 | PVE::Storage::volume_snapshot_rollback($storecfg, $drive->{file}, $snapname); | |
397 | } | |
398 | ||
399 | sub __snapshot_rollback_vm_stop { | |
400 | my ($class, $vmid) = @_; | |
401 | ||
402 | my $storecfg = PVE::Storage::config(); | |
403 | PVE::QemuServer::vm_stop($storecfg, $vmid, undef, undef, 5, undef, undef); | |
404 | } | |
405 | ||
406 | sub __snapshot_rollback_vm_start { | |
58b1a8d7 | 407 | my ($class, $vmid, $vmstate, $data) = @_; |
b2c9558d FG |
408 | |
409 | my $storecfg = PVE::Storage::config(); | |
0c498cca FG |
410 | my $params = { |
411 | statefile => $vmstate, | |
412 | forcemachine => $data->{forcemachine}, | |
ea1c2110 | 413 | forcecpu => $data->{forcecpu}, |
0c498cca FG |
414 | }; |
415 | PVE::QemuServer::vm_start($storecfg, $vmid, $params); | |
b2c9558d FG |
416 | } |
417 | ||
c4a54ed5 FG |
418 | sub __snapshot_rollback_get_unused { |
419 | my ($class, $conf, $snap) = @_; | |
420 | ||
421 | my $unused = []; | |
422 | ||
beed0e3b | 423 | $class->foreach_volume($conf, sub { |
c4a54ed5 FG |
424 | my ($vs, $volume) = @_; |
425 | ||
426 | return if PVE::QemuServer::drive_is_cdrom($volume); | |
427 | ||
428 | my $found = 0; | |
429 | my $volid = $volume->{file}; | |
430 | ||
beed0e3b | 431 | $class->foreach_volume($snap, sub { |
c4a54ed5 FG |
432 | my ($ds, $drive) = @_; |
433 | ||
434 | return if $found; | |
435 | return if PVE::QemuServer::drive_is_cdrom($drive); | |
436 | ||
437 | $found = 1 | |
438 | if ($drive->{file} && $drive->{file} eq $volid); | |
439 | }); | |
440 | ||
441 | push @$unused, $volid if !$found; | |
442 | }); | |
443 | ||
444 | return $unused; | |
445 | } | |
446 | ||
ffda963f FG |
447 | # END implemented abstract methods from PVE::AbstractConfig |
448 | ||
449 | 1; |