]>
Commit | Line | Data |
---|---|---|
53c25521 FG |
1 | package PVE::AbstractConfig; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
6 | use PVE::Tools qw(lock_file lock_file_full); | |
7 | use PVE::INotify; | |
8 | ||
9 | my $nodename = PVE::INotify::nodename(); | |
10 | ||
11 | # Printable string, currently either "VM" or "CT" | |
12 | sub guest_type { | |
13 | my ($class) = @_; | |
14 | die "abstract method - implement me "; | |
15 | } | |
16 | ||
17 | sub __config_max_unused_disks { | |
18 | my ($class) = @_; | |
19 | ||
20 | die "implement me - abstract method\n"; | |
21 | } | |
22 | ||
23 | # Path to the flock file for this VM/CT | |
24 | sub config_file_lock { | |
25 | my ($class, $vmid) = @_; | |
26 | die "abstract method - implement me"; | |
27 | } | |
28 | ||
29 | # Relative config file path for this VM/CT in CFS | |
30 | sub cfs_config_path { | |
31 | my ($class, $vmid, $node) = @_; | |
32 | die "abstract method - implement me"; | |
33 | } | |
34 | ||
35 | # Absolute config file path for this VM/CT | |
36 | sub config_file { | |
37 | my ($class, $vmid, $node) = @_; | |
38 | ||
39 | my $cfspath = $class->cfs_config_path($vmid, $node); | |
40 | return "/etc/pve/$cfspath"; | |
41 | } | |
42 | ||
43 | # Read and parse config file for this VM/CT | |
44 | sub load_config { | |
45 | my ($class, $vmid, $node) = @_; | |
46 | ||
47 | $node = $nodename if !$node; | |
48 | my $cfspath = $class->cfs_config_path($vmid, $node); | |
49 | ||
50 | my $conf = PVE::Cluster::cfs_read_file($cfspath); | |
51 | die "Configuration file '$cfspath' does not exist\n" | |
52 | if !defined($conf); | |
53 | ||
54 | return $conf; | |
55 | } | |
56 | ||
57 | # Generate and write config file for this VM/CT | |
58 | sub write_config { | |
59 | my ($class, $vmid, $conf) = @_; | |
60 | ||
61 | my $cfspath = $class->cfs_config_path($vmid); | |
62 | ||
63 | PVE::Cluster::cfs_write_file($cfspath, $conf); | |
64 | } | |
65 | ||
66 | # Lock config file using flock, run $code with @param, unlock config file. | |
67 | # $timeout is the maximum time to aquire the flock | |
68 | sub lock_config_full { | |
69 | my ($class, $vmid, $timeout, $code, @param) = @_; | |
70 | ||
71 | my $filename = $class->config_file_lock($vmid); | |
72 | ||
73 | my $res = lock_file($filename, $timeout, $code, @param); | |
74 | ||
75 | die $@ if $@; | |
76 | ||
77 | return $res; | |
78 | } | |
79 | ||
80 | # Lock config file using flock, run $code with @param, unlock config file. | |
81 | # $timeout is the maximum time to aquire the flock | |
82 | # $shared eq 1 creates a non-exclusive ("read") flock | |
83 | sub lock_config_mode { | |
84 | my ($class, $vmid, $timeout, $shared, $code, @param) = @_; | |
85 | ||
86 | my $filename = $class->config_file_lock($vmid); | |
87 | ||
88 | my $res = lock_file_full($filename, $timeout, $shared, $code, @param); | |
89 | ||
90 | die $@ if $@; | |
91 | ||
92 | return $res; | |
93 | } | |
94 | ||
95 | # Lock config file using flock, run $code with @param, unlock config file. | |
96 | sub lock_config { | |
97 | my ($class, $vmid, $code, @param) = @_; | |
98 | ||
99 | return $class->lock_config_full($vmid, 10, $code, @param); | |
100 | } | |
101 | ||
102 | # Checks whether the config is locked with the lock parameter | |
103 | sub check_lock { | |
104 | my ($class, $conf) = @_; | |
105 | ||
106 | die $class->guest_type()." is locked ($conf->{lock})\n" if $conf->{lock}; | |
107 | } | |
108 | ||
109 | # Returns whether the config is locked with the lock parameter, also checks | |
110 | # whether the lock value is correct if the optional $lock is set. | |
111 | sub has_lock { | |
112 | my ($class, $conf, $lock) = @_; | |
113 | ||
114 | return $conf->{lock} && (!defined($lock) || $lock eq $conf->{lock}); | |
115 | } | |
116 | ||
117 | # Sets the lock parameter for this VM/CT's config to $lock. | |
118 | sub set_lock { | |
119 | my ($class, $vmid, $lock) = @_; | |
120 | ||
121 | my $conf; | |
122 | $class->lock_config($vmid, sub { | |
123 | $conf = $class->load_config($vmid); | |
124 | $class->check_lock($conf); | |
125 | $conf->{lock} = $lock; | |
126 | $class->write_config($vmid, $conf); | |
127 | }); | |
128 | return $conf; | |
129 | } | |
130 | ||
131 | # Removes the lock parameter for this VM/CT's config, also checks whether | |
132 | # the lock value is correct if the optional $lock is set. | |
133 | sub remove_lock { | |
134 | my ($class, $vmid, $lock) = @_; | |
135 | ||
136 | $class->lock_config($vmid, sub { | |
137 | my $conf = $class->load_config($vmid); | |
138 | if (!$conf->{lock}) { | |
34f860a2 FG |
139 | my $lockstring = defined($lock) ? "'$lock' " : "any"; |
140 | die "no lock found trying to remove $lockstring lock\n"; | |
53c25521 | 141 | } elsif (defined($lock) && $conf->{lock} ne $lock) { |
34f860a2 | 142 | die "found lock '$conf->{lock}' trying to remove '$lock' lock\n"; |
53c25521 FG |
143 | } |
144 | delete $conf->{lock}; | |
145 | $class->write_config($vmid, $conf); | |
146 | }); | |
147 | } | |
148 | ||
149 | # Checks whether protection mode is enabled for this VM/CT. | |
150 | sub check_protection { | |
151 | my ($class, $conf, $err_msg) = @_; | |
152 | ||
153 | if ($conf->{protection}) { | |
154 | die "$err_msg - protection mode enabled\n"; | |
155 | } | |
156 | } | |
157 | ||
158 | # Adds an unused volume to $config, if possible. | |
159 | sub add_unused_volume { | |
160 | my ($class, $config, $volid) = @_; | |
161 | ||
162 | my $key; | |
163 | for (my $ind = $class->__config_max_unused_disks() - 1; $ind >= 0; $ind--) { | |
164 | my $test = "unused$ind"; | |
165 | if (my $vid = $config->{$test}) { | |
166 | return if $vid eq $volid; # do not add duplicates | |
167 | } else { | |
168 | $key = $test; | |
169 | } | |
170 | } | |
171 | ||
172 | die "Too many unused volumes - please delete them first.\n" if !$key; | |
173 | ||
174 | $config->{$key} = $volid; | |
175 | ||
176 | return $key; | |
177 | } | |
178 | ||
179 | # Returns whether the template parameter is set in $conf. | |
180 | sub is_template { | |
181 | my ($class, $conf) = @_; | |
182 | ||
183 | return 1 if defined $conf->{template} && $conf->{template} == 1; | |
184 | } | |
185 | ||
186 | # Checks whether $feature is availabe for the referenced volumes in $conf. | |
187 | # Note: depending on the parameters, some volumes may be skipped! | |
188 | sub has_feature { | |
189 | my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_; | |
190 | die "implement me - abstract method\n"; | |
191 | } | |
192 | ||
193 | # Internal snapshots | |
194 | ||
195 | # NOTE: Snapshot create/delete involves several non-atomic | |
196 | # actions, and can take a long time. | |
197 | # So we try to avoid locking the file and use the 'lock' variable | |
198 | # inside the config file instead. | |
199 | ||
200 | # Save the vmstate (RAM). | |
201 | sub __snapshot_save_vmstate { | |
202 | my ($class, $vmid, $conf, $snapname, $storecfg) = @_; | |
203 | die "implement me - abstract method\n"; | |
204 | } | |
205 | ||
206 | # Check whether the VM/CT is running. | |
207 | sub __snapshot_check_running { | |
208 | my ($class, $vmid) = @_; | |
209 | die "implement me - abstract method\n"; | |
210 | } | |
211 | ||
212 | # Check whether we need to freeze the VM/CT | |
213 | sub __snapshot_check_freeze_needed { | |
214 | my ($sself, $vmid, $config, $save_vmstate) = @_; | |
215 | die "implement me - abstract method\n"; | |
216 | } | |
217 | ||
218 | # Freeze or unfreeze the VM/CT. | |
219 | sub __snapshot_freeze { | |
220 | my ($class, $vmid, $unfreeze) = @_; | |
221 | ||
222 | die "abstract method - implement me\n"; | |
223 | } | |
224 | ||
ba9db5df FG |
225 | # Code run before and after creating all the volume snapshots |
226 | # base: noop | |
227 | sub __snapshot_create_vol_snapshots_hook { | |
228 | my ($class, $vmid, $snap, $running, $hook) = @_; | |
229 | ||
230 | return; | |
231 | } | |
232 | ||
53c25521 FG |
233 | # Create the volume snapshots for the VM/CT. |
234 | sub __snapshot_create_vol_snapshot { | |
235 | my ($class, $vmid, $vs, $volume, $snapname) = @_; | |
236 | ||
237 | die "abstract method - implement me\n"; | |
238 | } | |
239 | ||
240 | # Remove a drive from the snapshot config. | |
241 | sub __snapshot_delete_remove_drive { | |
242 | my ($class, $snap, $remove_drive) = @_; | |
243 | ||
244 | die "abstract method - implement me\n"; | |
245 | } | |
246 | ||
247 | # Delete the vmstate file/drive | |
248 | sub __snapshot_delete_vmstate_file { | |
249 | my ($class, $snap, $force) = @_; | |
250 | ||
251 | die "abstract method - implement me\n"; | |
252 | } | |
253 | ||
254 | # Delete a volume snapshot | |
255 | sub __snapshot_delete_vol_snapshot { | |
256 | my ($class, $vmid, $vs, $volume, $snapname) = @_; | |
257 | ||
258 | die "abstract method - implement me\n"; | |
259 | } | |
260 | ||
261 | # Checks whether a volume snapshot is possible for this volume. | |
262 | sub __snapshot_rollback_vol_possible { | |
263 | my ($class, $volume, $snapname) = @_; | |
264 | ||
265 | die "abstract method - implement me\n"; | |
266 | } | |
267 | ||
268 | # Rolls back this volume. | |
269 | sub __snapshot_rollback_vol_rollback { | |
270 | my ($class, $volume, $snapname) = @_; | |
271 | ||
272 | die "abstract method - implement me\n"; | |
273 | } | |
274 | ||
275 | # Stops the VM/CT for a rollback. | |
276 | sub __snapshot_rollback_vm_stop { | |
277 | my ($class, $vmid) = @_; | |
278 | ||
279 | die "abstract method - implement me\n"; | |
280 | } | |
281 | ||
282 | # Start the VM/CT after a rollback with restored vmstate. | |
283 | sub __snapshot_rollback_vm_start { | |
284 | my ($class, $vmid, $vmstate, $forcemachine); | |
285 | ||
286 | die "abstract method - implement me\n"; | |
287 | } | |
288 | ||
e561bc74 FG |
289 | # Get list of volume IDs which are referenced in $conf, but not in $snap. |
290 | sub __snapshot_rollback_get_unused { | |
291 | my ($class, $conf, $snap) = @_; | |
292 | ||
293 | die "abstract method - implement me\n"; | |
294 | } | |
295 | ||
53c25521 FG |
296 | # Iterate over all configured volumes, calling $func for each key/value pair. |
297 | sub __snapshot_foreach_volume { | |
298 | my ($class, $conf, $func) = @_; | |
299 | ||
300 | die "abstract method - implement me\n"; | |
301 | } | |
302 | ||
303 | # Copy the current config $source to the snapshot config $dest | |
304 | sub __snapshot_copy_config { | |
305 | my ($class, $source, $dest) = @_; | |
306 | ||
307 | foreach my $k (keys %$source) { | |
308 | next if $k eq 'snapshots'; | |
309 | next if $k eq 'snapstate'; | |
310 | next if $k eq 'snaptime'; | |
311 | next if $k eq 'vmstate'; | |
312 | next if $k eq 'lock'; | |
313 | next if $k eq 'digest'; | |
314 | next if $k eq 'description'; | |
315 | next if $k =~ m/^unused\d+$/; | |
316 | ||
317 | $dest->{$k} = $source->{$k}; | |
318 | } | |
319 | }; | |
320 | ||
321 | # Apply the snapshot config $snap to the config $conf (rollback) | |
322 | sub __snapshot_apply_config { | |
323 | my ($class, $conf, $snap) = @_; | |
324 | ||
325 | # copy snapshot list | |
326 | my $newconf = { | |
327 | snapshots => $conf->{snapshots}, | |
328 | }; | |
329 | ||
330 | # keep description and list of unused disks | |
331 | foreach my $k (keys %$conf) { | |
332 | next if !($k =~ m/^unused\d+$/ || $k eq 'description'); | |
333 | $newconf->{$k} = $conf->{$k}; | |
334 | } | |
335 | ||
336 | $class->__snapshot_copy_config($snap, $newconf); | |
337 | ||
338 | return $newconf; | |
339 | } | |
340 | ||
341 | # Prepares the configuration for snapshotting. | |
342 | sub __snapshot_prepare { | |
343 | my ($class, $vmid, $snapname, $save_vmstate, $comment) = @_; | |
344 | ||
345 | my $snap; | |
346 | ||
347 | my $updatefn = sub { | |
348 | ||
349 | my $conf = $class->load_config($vmid); | |
350 | ||
351 | die "you can't take a snapshot if it's a template\n" | |
352 | if $class->is_template($conf); | |
353 | ||
354 | $class->check_lock($conf); | |
355 | ||
356 | $conf->{lock} = 'snapshot'; | |
357 | ||
358 | die "snapshot name '$snapname' already used\n" | |
359 | if defined($conf->{snapshots}->{$snapname}); | |
360 | ||
361 | my $storecfg = PVE::Storage::config(); | |
362 | die "snapshot feature is not available\n" | |
363 | if !$class->has_feature('snapshot', $conf, $storecfg, undef, undef, $snapname eq 'vzdump'); | |
364 | ||
365 | $snap = $conf->{snapshots}->{$snapname} = {}; | |
366 | ||
367 | if ($save_vmstate && $class->__snapshot_check_running($vmid)) { | |
368 | $class->__snapshot_save_vmstate($vmid, $conf, $snapname, $storecfg); | |
369 | } | |
370 | ||
371 | $class->__snapshot_copy_config($conf, $snap); | |
372 | ||
373 | $snap->{snapstate} = "prepare"; | |
374 | $snap->{snaptime} = time(); | |
375 | $snap->{description} = $comment if $comment; | |
376 | ||
377 | $class->write_config($vmid, $conf); | |
378 | }; | |
379 | ||
380 | $class->lock_config($vmid, $updatefn); | |
381 | ||
382 | return $snap; | |
383 | } | |
384 | ||
385 | # Commits the configuration after snapshotting. | |
386 | sub __snapshot_commit { | |
387 | my ($class, $vmid, $snapname) = @_; | |
388 | ||
389 | my $updatefn = sub { | |
390 | ||
391 | my $conf = $class->load_config($vmid); | |
392 | ||
393 | die "missing snapshot lock\n" | |
394 | if !($conf->{lock} && $conf->{lock} eq 'snapshot'); | |
395 | ||
396 | my $snap = $conf->{snapshots}->{$snapname}; | |
397 | die "snapshot '$snapname' does not exist\n" if !defined($snap); | |
398 | ||
399 | die "wrong snapshot state\n" | |
400 | if !($snap->{snapstate} && $snap->{snapstate} eq "prepare"); | |
401 | ||
402 | delete $snap->{snapstate}; | |
403 | delete $conf->{lock}; | |
404 | ||
405 | $conf->{parent} = $snapname; | |
406 | ||
407 | $class->write_config($vmid, $conf); | |
408 | }; | |
409 | ||
410 | $class->lock_config($vmid, $updatefn); | |
411 | } | |
412 | ||
413 | # Creates a snapshot for the VM/CT. | |
414 | sub snapshot_create { | |
415 | my ($class, $vmid, $snapname, $save_vmstate, $comment) = @_; | |
416 | ||
417 | my $snap = $class->__snapshot_prepare($vmid, $snapname, $save_vmstate, $comment); | |
418 | ||
419 | $save_vmstate = 0 if !$snap->{vmstate}; | |
420 | ||
421 | my $conf = $class->load_config($vmid); | |
422 | ||
423 | my ($running, $freezefs) = $class->__snapshot_check_freeze_needed($vmid, $conf, $snap->{vmstate}); | |
424 | ||
425 | my $drivehash = {}; | |
426 | ||
427 | eval { | |
428 | if ($freezefs) { | |
429 | $class->__snapshot_freeze($vmid, 0); | |
430 | } | |
431 | ||
ba9db5df FG |
432 | $class->__snapshot_create_vol_snapshots_hook($vmid, $snap, $running, "before"); |
433 | ||
53c25521 FG |
434 | $class->__snapshot_foreach_volume($snap, sub { |
435 | my ($vs, $volume) = @_; | |
436 | ||
437 | $class->__snapshot_create_vol_snapshot($vmid, $vs, $volume, $snapname); | |
438 | $drivehash->{$vs} = 1; | |
439 | }); | |
440 | }; | |
441 | my $err = $@; | |
442 | ||
443 | if ($running) { | |
ba9db5df | 444 | $class->__snapshot_create_vol_snapshots_hook($vmid, $snap, $running, "after"); |
53c25521 FG |
445 | if ($freezefs) { |
446 | $class->__snapshot_freeze($vmid, 1); | |
447 | } | |
ba9db5df | 448 | $class->__snapshot_create_vol_snapshots_hook($vmid, $snap, $running, "after-unfreeze"); |
53c25521 FG |
449 | } |
450 | ||
451 | if ($err) { | |
452 | warn "snapshot create failed: starting cleanup\n"; | |
453 | eval { $class->snapshot_delete($vmid, $snapname, 1, $drivehash); }; | |
454 | warn "$@" if $@; | |
455 | die "$err\n"; | |
456 | } | |
457 | ||
458 | $class->__snapshot_commit($vmid, $snapname); | |
459 | } | |
460 | ||
461 | # Deletes a snapshot. | |
462 | # Note: $drivehash is only set when called from snapshot_create. | |
463 | sub snapshot_delete { | |
464 | my ($class, $vmid, $snapname, $force, $drivehash) = @_; | |
465 | ||
466 | my $prepare = 1; | |
467 | ||
468 | my $snap; | |
469 | my $unused = []; | |
470 | ||
471 | my $unlink_parent = sub { | |
472 | my ($confref, $new_parent) = @_; | |
473 | ||
474 | if ($confref->{parent} && $confref->{parent} eq $snapname) { | |
475 | if ($new_parent) { | |
476 | $confref->{parent} = $new_parent; | |
477 | } else { | |
478 | delete $confref->{parent}; | |
479 | } | |
480 | } | |
481 | }; | |
482 | ||
483 | my $updatefn = sub { | |
484 | my ($remove_drive) = @_; | |
485 | ||
486 | my $conf = $class->load_config($vmid); | |
487 | ||
488 | if (!$drivehash) { | |
489 | $class->check_lock($conf); | |
490 | die "you can't delete a snapshot if vm is a template\n" | |
491 | if $class->is_template($conf); | |
492 | } | |
493 | ||
494 | $snap = $conf->{snapshots}->{$snapname}; | |
495 | ||
496 | die "snapshot '$snapname' does not exist\n" if !defined($snap); | |
497 | ||
498 | # remove parent refs | |
499 | if (!$prepare) { | |
500 | &$unlink_parent($conf, $snap->{parent}); | |
501 | foreach my $sn (keys %{$conf->{snapshots}}) { | |
502 | next if $sn eq $snapname; | |
503 | &$unlink_parent($conf->{snapshots}->{$sn}, $snap->{parent}); | |
504 | } | |
505 | } | |
506 | ||
507 | if ($remove_drive) { | |
508 | $class->__snapshot_delete_remove_drive($snap, $remove_drive); | |
509 | } | |
510 | ||
511 | if ($prepare) { | |
512 | $snap->{snapstate} = 'delete'; | |
513 | } else { | |
514 | delete $conf->{snapshots}->{$snapname}; | |
515 | delete $conf->{lock} if $drivehash; | |
516 | foreach my $volid (@$unused) { | |
517 | $class->add_unused_volume($conf, $volid); | |
518 | } | |
519 | } | |
520 | ||
521 | $class->write_config($vmid, $conf); | |
522 | }; | |
523 | ||
524 | $class->lock_config($vmid, $updatefn); | |
525 | ||
526 | # now remove vmstate file | |
527 | if ($snap->{vmstate}) { | |
528 | $class->__snapshot_delete_vmstate_file($snap, $force); | |
529 | ||
530 | # save changes (remove vmstate from snapshot) | |
531 | $class->lock_config($vmid, $updatefn, 'vmstate') if !$force; | |
532 | }; | |
533 | ||
534 | # now remove all volume snapshots | |
535 | $class->__snapshot_foreach_volume($snap, sub { | |
536 | my ($vs, $volume) = @_; | |
537 | ||
538 | return if $snapname eq 'vzdump' && $vs ne 'rootfs' && !$volume->{backup}; | |
539 | if (!$drivehash || $drivehash->{$vs}) { | |
24623202 | 540 | eval { $class->__snapshot_delete_vol_snapshot($vmid, $vs, $volume, $snapname, $unused); }; |
53c25521 FG |
541 | if (my $err = $@) { |
542 | die $err if !$force; | |
543 | warn $err; | |
544 | } | |
545 | } | |
546 | ||
547 | # save changes (remove mp from snapshot) | |
548 | $class->lock_config($vmid, $updatefn, $vs) if !$force; | |
53c25521 FG |
549 | }); |
550 | ||
551 | # now cleanup config | |
552 | $prepare = 0; | |
553 | $class->lock_config($vmid, $updatefn); | |
554 | } | |
555 | ||
556 | # Rolls back to a given snapshot. | |
557 | sub snapshot_rollback { | |
558 | my ($class, $vmid, $snapname) = @_; | |
559 | ||
560 | my $prepare = 1; | |
561 | ||
562 | my $storecfg = PVE::Storage::config(); | |
563 | ||
564 | my $conf = $class->load_config($vmid); | |
565 | ||
566 | my $get_snapshot_config = sub { | |
567 | ||
568 | die "you can't rollback if vm is a template\n" if $class->is_template($conf); | |
569 | ||
570 | my $res = $conf->{snapshots}->{$snapname}; | |
571 | ||
572 | die "snapshot '$snapname' does not exist\n" if !defined($res); | |
573 | ||
574 | return $res; | |
575 | }; | |
576 | ||
577 | my $snap = &$get_snapshot_config(); | |
578 | ||
579 | $class->__snapshot_foreach_volume($snap, sub { | |
580 | my ($vs, $volume) = @_; | |
581 | ||
582 | $class->__snapshot_rollback_vol_possible($volume, $snapname); | |
583 | }); | |
584 | ||
585 | my $updatefn = sub { | |
586 | ||
587 | $conf = $class->load_config($vmid); | |
588 | ||
589 | $snap = &$get_snapshot_config(); | |
590 | ||
591 | die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n" | |
592 | if $snap->{snapstate}; | |
593 | ||
594 | if ($prepare) { | |
595 | $class->check_lock($conf); | |
596 | $class->__snapshot_rollback_vm_stop($vmid); | |
597 | } | |
598 | ||
599 | die "unable to rollback vm $vmid: vm is running\n" | |
600 | if $class->__snapshot_check_running($vmid); | |
601 | ||
602 | if ($prepare) { | |
603 | $conf->{lock} = 'rollback'; | |
604 | } else { | |
605 | die "got wrong lock\n" if !($conf->{lock} && $conf->{lock} eq 'rollback'); | |
606 | delete $conf->{lock}; | |
607 | } | |
608 | ||
609 | # machine only relevant for Qemu | |
610 | my $forcemachine; | |
611 | ||
612 | if (!$prepare) { | |
e561bc74 FG |
613 | my $unused = $class->__snapshot_rollback_get_unused($conf, $snap); |
614 | ||
615 | foreach my $volid (@$unused) { | |
616 | $class->add_unused_volume($conf, $volid); | |
617 | } | |
618 | ||
53c25521 FG |
619 | my $has_machine_config = defined($conf->{machine}); |
620 | ||
621 | # copy snapshot config to current config | |
622 | $conf = $class->__snapshot_apply_config($conf, $snap); | |
623 | $conf->{parent} = $snapname; | |
624 | ||
625 | # Note: old code did not store 'machine', so we try to be smart | |
626 | # and guess the snapshot was generated with kvm 1.4 (pc-i440fx-1.4). | |
627 | $forcemachine = $conf->{machine} || 'pc-i440fx-1.4'; | |
628 | # we remove the 'machine' configuration if not explicitly specified | |
629 | # in the original config. | |
630 | delete $conf->{machine} if $snap->{vmstate} && !$has_machine_config; | |
631 | } | |
632 | ||
633 | $class->write_config($vmid, $conf); | |
634 | ||
635 | if (!$prepare && $snap->{vmstate}) { | |
636 | $class->__snapshot_rollback_vm_start($vmid, $snap->{vmstate}, $forcemachine); | |
637 | } | |
638 | }; | |
639 | ||
640 | $class->lock_config($vmid, $updatefn); | |
641 | ||
642 | $class->__snapshot_foreach_volume($snap, sub { | |
643 | my ($vs, $volume) = @_; | |
644 | ||
645 | $class->__snapshot_rollback_vol_rollback($volume, $snapname); | |
646 | }); | |
647 | ||
648 | $prepare = 0; | |
649 | $class->lock_config($vmid, $updatefn); | |
650 | } | |
651 | ||
652 | 1; |