]>
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; | |
8 | use PVE::QemuServer; | |
9 | use PVE::Storage; | |
10 | use PVE::Tools; | |
11 | ||
ffda963f FG |
12 | use base qw(PVE::AbstractConfig); |
13 | ||
14 | my $nodename = PVE::INotify::nodename(); | |
15 | ||
16 | mkdir "/etc/pve/nodes/$nodename"; | |
17 | my $confdir = "/etc/pve/nodes/$nodename/qemu-server"; | |
18 | mkdir $confdir; | |
19 | ||
20 | my $lock_dir = "/var/lock/qemu-server"; | |
21 | mkdir $lock_dir; | |
22 | ||
23 | my $MAX_UNUSED_DISKS = 8; | |
24 | ||
25 | # BEGIN implemented abstract methods from PVE::AbstractConfig | |
26 | ||
27 | sub guest_type { | |
28 | return "VM"; | |
29 | } | |
30 | ||
31 | sub __config_max_unused_disks { | |
32 | my ($class) =@_; | |
33 | ||
34 | return $MAX_UNUSED_DISKS; | |
35 | } | |
36 | ||
37 | sub config_file_lock { | |
38 | my ($class, $vmid) = @_; | |
39 | ||
40 | return "$lock_dir/lock-$vmid.conf"; | |
41 | } | |
42 | ||
43 | sub cfs_config_path { | |
44 | my ($class, $vmid, $node) = @_; | |
45 | ||
46 | $node = $nodename if !$node; | |
47 | return "nodes/$node/qemu-server/$vmid.conf"; | |
48 | } | |
49 | ||
b2c9558d FG |
50 | sub has_feature { |
51 | my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_; | |
52 | ||
53 | my $err; | |
54 | PVE::QemuServer::foreach_drive($conf, sub { | |
55 | my ($ds, $drive) = @_; | |
56 | ||
57 | return if PVE::QemuServer::drive_is_cdrom($drive); | |
58 | return if $backup_only && !$drive->{backup}; | |
59 | my $volid = $drive->{file}; | |
60 | $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $volid, $snapname, $running); | |
61 | }); | |
62 | ||
63 | return $err ? 0 : 1; | |
64 | } | |
65 | ||
66 | sub __snapshot_save_vmstate { | |
67 | my ($class, $vmid, $conf, $snapname, $storecfg) = @_; | |
68 | ||
69 | my $snap = $conf->{snapshots}->{$snapname}; | |
70 | ||
71 | my $target; | |
72 | ||
73 | # search shared storage first | |
74 | PVE::QemuServer::foreach_writable_storage($conf, sub { | |
75 | my ($sid) = @_; | |
76 | my $scfg = PVE::Storage::storage_config($storecfg, $sid); | |
77 | return if !$scfg->{shared}; | |
78 | ||
79 | $target = $sid if !$target || $scfg->{path}; # prefer file based storage | |
80 | }); | |
81 | ||
82 | if (!$target) { | |
83 | # now search local storage | |
84 | PVE::QemuServer::foreach_writable_storage($conf, sub { | |
85 | my ($sid) = @_; | |
86 | my $scfg = PVE::Storage::storage_config($storecfg, $sid); | |
87 | return if $scfg->{shared}; | |
88 | ||
89 | $target = $sid if !$target || $scfg->{path}; # prefer file based storage; | |
90 | }); | |
91 | } | |
92 | ||
93 | $target = 'local' if !$target; | |
94 | ||
95 | my $driver_state_size = 500; # assume 32MB is enough to safe all driver state; | |
96 | # we abort live save after $conf->{memory}, so we need at max twice that space | |
97 | my $size = $conf->{memory}*2 + $driver_state_size; | |
98 | ||
99 | my $name = "vm-$vmid-state-$snapname"; | |
100 | my $scfg = PVE::Storage::storage_config($storecfg, $target); | |
101 | $name .= ".raw" if $scfg->{path}; # add filename extension for file base storage | |
102 | $snap->{vmstate} = PVE::Storage::vdisk_alloc($storecfg, $target, $vmid, 'raw', $name, $size*1024); | |
103 | # always overwrite machine if we save vmstate. This makes sure we | |
104 | # can restore it later using correct machine type | |
105 | $snap->{machine} = PVE::QemuServer::get_current_qemu_machine($vmid); | |
106 | } | |
107 | ||
108 | sub __snapshot_check_running { | |
109 | my ($class, $vmid) = @_; | |
110 | return PVE::QemuServer::check_running($vmid); | |
111 | } | |
112 | ||
113 | sub __snapshot_check_freeze_needed { | |
114 | my ($class, $vmid, $config, $save_vmstate) = @_; | |
115 | ||
116 | my $running = $class->__snapshot_check_running($vmid); | |
117 | if ($save_vmstate) { | |
118 | return ($running, $running && $config->{agent} && PVE::QemuServer::qga_check_running($vmid)); | |
119 | } else { | |
120 | return ($running, 0); | |
121 | } | |
122 | } | |
123 | ||
124 | sub __snapshot_freeze { | |
125 | my ($class, $vmid, $unfreeze) = @_; | |
126 | ||
127 | if ($unfreeze) { | |
128 | eval { PVE::QemuServer::vm_mon_cmd($vmid, "guest-fsfreeze-thaw"); }; | |
129 | warn "guest-fsfreeze-thaw problems - $@" if $@; | |
130 | } else { | |
131 | eval { PVE::QemuServer::vm_mon_cmd($vmid, "guest-fsfreeze-freeze"); }; | |
132 | warn "guest-fsfreeze-freeze problems - $@" if $@; | |
133 | } | |
134 | } | |
135 | ||
136 | sub __snapshot_create_vol_snapshots_hook { | |
137 | my ($class, $vmid, $snap, $running, $hook) = @_; | |
138 | ||
139 | if ($running) { | |
3a8deb55 AD |
140 | my $storecfg = PVE::Storage::config(); |
141 | ||
b2c9558d FG |
142 | if ($hook eq "before") { |
143 | if ($snap->{vmstate}) { | |
b2c9558d | 144 | my $path = PVE::Storage::path($storecfg, $snap->{vmstate}); |
3a8deb55 AD |
145 | PVE::Storage::activate_volumes($storecfg, [$snap->{vmstate}]); |
146 | ||
b2c9558d FG |
147 | PVE::QemuServer::vm_mon_cmd($vmid, "savevm-start", statefile => $path); |
148 | for(;;) { | |
149 | my $stat = PVE::QemuServer::vm_mon_cmd_nocheck($vmid, "query-savevm"); | |
150 | if (!$stat->{status}) { | |
151 | die "savevm not active\n"; | |
152 | } elsif ($stat->{status} eq 'active') { | |
153 | sleep(1); | |
154 | next; | |
155 | } elsif ($stat->{status} eq 'completed') { | |
156 | last; | |
157 | } else { | |
158 | die "query-savevm returned status '$stat->{status}'\n"; | |
159 | } | |
160 | } | |
161 | } else { | |
162 | PVE::QemuServer::vm_mon_cmd($vmid, "savevm-start"); | |
163 | } | |
164 | } elsif ($hook eq "after") { | |
3a8deb55 AD |
165 | eval { |
166 | PVE::QemuServer::vm_mon_cmd($vmid, "savevm-end"); | |
167 | PVE::Storage::deactivate_volumes($storecfg, [$snap->{vmstate}]) if $snap->{vmstate}; | |
168 | }; | |
b2c9558d FG |
169 | warn $@ if $@; |
170 | } elsif ($hook eq "after-freeze") { | |
171 | # savevm-end is async, we need to wait | |
172 | for (;;) { | |
173 | my $stat = PVE::QemuServer::vm_mon_cmd_nocheck($vmid, "query-savevm"); | |
174 | if (!$stat->{bytes}) { | |
175 | last; | |
176 | } else { | |
177 | print "savevm not yet finished\n"; | |
178 | sleep(1); | |
179 | next; | |
180 | } | |
181 | } | |
182 | } | |
183 | } | |
184 | } | |
185 | ||
186 | sub __snapshot_create_vol_snapshot { | |
187 | my ($class, $vmid, $ds, $drive, $snapname) = @_; | |
188 | ||
189 | return if PVE::QemuServer::drive_is_cdrom($drive); | |
190 | ||
191 | my $volid = $drive->{file}; | |
192 | my $device = "drive-$ds"; | |
193 | my $storecfg = PVE::Storage::config(); | |
194 | ||
195 | PVE::QemuServer::qemu_volume_snapshot($vmid, $device, $storecfg, $volid, $snapname); | |
196 | } | |
197 | ||
198 | sub __snapshot_delete_remove_drive { | |
199 | my ($class, $snap, $remove_drive) = @_; | |
200 | ||
201 | if ($remove_drive eq 'vmstate') { | |
202 | delete $snap->{$remove_drive}; | |
203 | } else { | |
204 | my $drive = PVE::QemuServer::parse_drive($remove_drive, $snap->{$remove_drive}); | |
205 | return if PVE::QemuServer::drive_is_cdrom($drive); | |
206 | ||
207 | my $volid = $drive->{file}; | |
208 | delete $snap->{$remove_drive}; | |
209 | $class->add_unused_volume($snap, $volid); | |
210 | } | |
211 | } | |
212 | ||
213 | sub __snapshot_delete_vmstate_file { | |
214 | my ($class, $snap, $force) = @_; | |
215 | ||
216 | my $storecfg = PVE::Storage::config(); | |
217 | ||
218 | eval { PVE::Storage::vdisk_free($storecfg, $snap->{vmstate}); }; | |
219 | if (my $err = $@) { | |
220 | die $err if !$force; | |
221 | warn $err; | |
222 | } | |
223 | } | |
224 | ||
225 | sub __snapshot_delete_vol_snapshot { | |
226 | my ($class, $vmid, $ds, $drive, $snapname, $unused) = @_; | |
227 | ||
228 | return if PVE::QemuServer::drive_is_cdrom($drive); | |
229 | my $storecfg = PVE::Storage::config(); | |
230 | my $volid = $drive->{file}; | |
231 | my $device = "drive-$ds"; | |
232 | ||
233 | PVE::QemuServer::qemu_volume_snapshot_delete($vmid, $device, $storecfg, $volid, $snapname); | |
234 | ||
235 | push @$unused, $volid; | |
236 | } | |
237 | ||
238 | sub __snapshot_rollback_vol_possible { | |
239 | my ($class, $drive, $snapname) = @_; | |
240 | ||
241 | return if PVE::QemuServer::drive_is_cdrom($drive); | |
242 | ||
243 | my $storecfg = PVE::Storage::config(); | |
244 | my $volid = $drive->{file}; | |
245 | ||
246 | PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname); | |
247 | } | |
248 | ||
249 | sub __snapshot_rollback_vol_rollback { | |
250 | my ($class, $drive, $snapname) = @_; | |
251 | ||
252 | return if PVE::QemuServer::drive_is_cdrom($drive); | |
253 | ||
254 | my $storecfg = PVE::Storage::config(); | |
255 | PVE::Storage::volume_snapshot_rollback($storecfg, $drive->{file}, $snapname); | |
256 | } | |
257 | ||
258 | sub __snapshot_rollback_vm_stop { | |
259 | my ($class, $vmid) = @_; | |
260 | ||
261 | my $storecfg = PVE::Storage::config(); | |
262 | PVE::QemuServer::vm_stop($storecfg, $vmid, undef, undef, 5, undef, undef); | |
263 | } | |
264 | ||
265 | sub __snapshot_rollback_vm_start { | |
266 | my ($class, $vmid, $vmstate, $forcemachine) = @_; | |
267 | ||
268 | my $storecfg = PVE::Storage::config(); | |
269 | my $statefile = PVE::Storage::path($storecfg, $vmstate); | |
270 | PVE::QemuServer::vm_start($storecfg, $vmid, $statefile, undef, undef, undef, $forcemachine); | |
271 | } | |
272 | ||
c4a54ed5 FG |
273 | sub __snapshot_rollback_get_unused { |
274 | my ($class, $conf, $snap) = @_; | |
275 | ||
276 | my $unused = []; | |
277 | ||
278 | $class->__snapshot_foreach_volume($conf, sub { | |
279 | my ($vs, $volume) = @_; | |
280 | ||
281 | return if PVE::QemuServer::drive_is_cdrom($volume); | |
282 | ||
283 | my $found = 0; | |
284 | my $volid = $volume->{file}; | |
285 | ||
286 | $class->__snapshot_foreach_volume($snap, sub { | |
287 | my ($ds, $drive) = @_; | |
288 | ||
289 | return if $found; | |
290 | return if PVE::QemuServer::drive_is_cdrom($drive); | |
291 | ||
292 | $found = 1 | |
293 | if ($drive->{file} && $drive->{file} eq $volid); | |
294 | }); | |
295 | ||
296 | push @$unused, $volid if !$found; | |
297 | }); | |
298 | ||
299 | return $unused; | |
300 | } | |
301 | ||
b2c9558d FG |
302 | sub __snapshot_foreach_volume { |
303 | my ($class, $conf, $func) = @_; | |
304 | ||
305 | PVE::QemuServer::foreach_drive($conf, $func); | |
306 | } | |
ffda963f FG |
307 | # END implemented abstract methods from PVE::AbstractConfig |
308 | ||
309 | 1; |