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