]> git.proxmox.com Git - pve-guest-common.git/blame - PVE/AbstractConfig.pm
refactor/cleanup remove_from_pending_delete sligthly
[pve-guest-common.git] / PVE / AbstractConfig.pm
CommitLineData
58a3c91c
FG
1package PVE::AbstractConfig;
2
3use strict;
4use warnings;
5
6use PVE::Tools qw(lock_file lock_file_full);
7use PVE::INotify;
8use PVE::Cluster;
9use PVE::Storage;
10
14f17b49
DM
11use PVE::ReplicationConfig;
12use PVE::Replication;
13
58a3c91c
FG
14my $nodename = PVE::INotify::nodename();
15
16# Printable string, currently either "VM" or "CT"
17sub guest_type {
18 my ($class) = @_;
19 die "abstract method - implement me ";
20}
21
22sub __config_max_unused_disks {
23 my ($class) = @_;
24
25 die "implement me - abstract method\n";
26}
27
28# Path to the flock file for this VM/CT
29sub config_file_lock {
30 my ($class, $vmid) = @_;
31 die "abstract method - implement me";
32}
33
34# Relative config file path for this VM/CT in CFS
35sub cfs_config_path {
36 my ($class, $vmid, $node) = @_;
37 die "abstract method - implement me";
38}
39
40# Absolute config file path for this VM/CT
41sub config_file {
42 my ($class, $vmid, $node) = @_;
43
44 my $cfspath = $class->cfs_config_path($vmid, $node);
45 return "/etc/pve/$cfspath";
46}
47
48# Read and parse config file for this VM/CT
49sub load_config {
50 my ($class, $vmid, $node) = @_;
51
52 $node = $nodename if !$node;
53 my $cfspath = $class->cfs_config_path($vmid, $node);
54
55 my $conf = PVE::Cluster::cfs_read_file($cfspath);
56 die "Configuration file '$cfspath' does not exist\n"
57 if !defined($conf);
58
59 return $conf;
60}
61
62# Generate and write config file for this VM/CT
63sub write_config {
64 my ($class, $vmid, $conf) = @_;
65
66 my $cfspath = $class->cfs_config_path($vmid);
67
68 PVE::Cluster::cfs_write_file($cfspath, $conf);
69}
70
810ee088
OB
71# Pending changes related
72
73sub parse_pending_delete {
74 my ($class, $data) = @_;
7b9d1ade
TL
75
76 return {} if !$data;
77
810ee088
OB
78 $data =~ s/[,;]/ /g;
79 $data =~ s/^\s+//;
7b9d1ade
TL
80
81 my $pending_deletions = {};
82 for my $entry (split(/\s+/, $data)) {
83 my ($force, $key) = $entry =~ /^(!?)(.*)$/;
84
85 $pending_deletions->{$key} = {
86 force => $force ? 1 : 0,
87 };
88 }
89
90 return $pending_deletions;
810ee088
OB
91}
92
93sub print_pending_delete {
94 my ($class, $delete_hash) = @_;
51088c75
TL
95
96 my $render_key = sub {
97 my $key = shift;
98 $key = "!$key" if $delete_hash->{$key}->{force};
99 return $key;
100 };
101
6a6ad855 102 join (',', map { $render_key->($_) } sort keys %$delete_hash);
810ee088
OB
103}
104
105sub add_to_pending_delete {
106 my ($class, $conf, $key, $force) = @_;
107
51e827b8
TL
108 $conf->{pending} //= {};
109 my $pending = $conf->{pending};
110 my $pending_delete_hash = $class->parse_pending_delete($pending->{delete});
111
112 $pending_delete_hash->{$key} = { force => $force };
113
114 $pending->{delete} = $class->print_pending_delete($pending_delete_hash);
115
116 return $conf;
810ee088
OB
117}
118
119sub remove_from_pending_delete {
120 my ($class, $conf, $key) = @_;
121
464a42c4
TL
122 my $pending = $conf->{pending};
123 my $pending_delete_hash = $class->parse_pending_delete($pending->{delete});
124
125 return $conf if ! exists $pending_delete_hash->{$key};
126
810ee088
OB
127 delete $pending_delete_hash->{$key};
128
129 if (%$pending_delete_hash) {
464a42c4 130 $pending->{delete} = $class->print_pending_delete($pending_delete_hash);
810ee088 131 } else {
464a42c4 132 delete $pending->{delete};
810ee088 133 }
464a42c4
TL
134
135 return $conf;
810ee088
OB
136}
137
138sub cleanup_pending {
139 my ($class, $conf) = @_;
140
141 # remove pending changes when nothing changed
142 my $changes;
143 foreach my $opt (keys %{$conf->{pending}}) {
144 next if $opt eq 'delete'; # just to be sure
145 if (defined($conf->{$opt}) && ($conf->{pending}->{$opt} eq $conf->{$opt})) {
146 $changes = 1;
147 delete $conf->{pending}->{$opt};
148 }
149 }
150
151 my $current_delete_hash = $class->parse_pending_delete($conf->{pending}->{delete});
152 my $pending_delete_hash = {};
153 while (my ($opt, $force) = each %$current_delete_hash) {
154 if (defined($conf->{$opt})) {
155 $pending_delete_hash->{$opt} = $force;
156 } else {
157 $changes = 1;
158 }
159 }
160
161 if (%$pending_delete_hash) {
162 $conf->{pending}->{delete} = $class->print_pending_delete($pending_delete_hash);
163 } else {
164 delete $conf->{pending}->{delete};
165 }
166
167 return $changes;
168}
169
61264e82
OB
170sub load_snapshot_config {
171 my ($class, $vmid, $snapname) = @_;
172
173 my $conf = $class->load_config($vmid);
174
175 my $snapshot = $conf->{snapshots}->{$snapname};
176 die "snapshot '$snapname' does not exist\n" if !defined($snapshot);
177
178 $snapshot->{digest} = $conf->{digest};
179
180 return $snapshot;
181
182}
183
184sub load_current_config {
185 my ($class, $vmid, $current) = @_;
186
187 my $conf = $class->load_config($vmid);
188
189 # take pending changes in
190 if (!$current) {
191 foreach my $opt (keys %{$conf->{pending}}) {
192 next if $opt eq 'delete';
193 my $value = $conf->{pending}->{$opt};
194 next if ref($value); # just to be sure
195 $conf->{$opt} = $value;
196 }
197 my $pending_delete_hash = $class->parse_pending_delete($conf->{pending}->{delete});
198 foreach my $opt (keys %$pending_delete_hash) {
199 delete $conf->{$opt} if $conf->{$opt};
200 }
201 }
202
203 delete $conf->{snapshots};
204 delete $conf->{pending};
205
206 return $conf;
207}
208
209
58a3c91c
FG
210# Lock config file using flock, run $code with @param, unlock config file.
211# $timeout is the maximum time to aquire the flock
212sub lock_config_full {
213 my ($class, $vmid, $timeout, $code, @param) = @_;
214
215 my $filename = $class->config_file_lock($vmid);
216
217 my $res = lock_file($filename, $timeout, $code, @param);
218
219 die $@ if $@;
220
221 return $res;
222}
223
cbbb06a5
TL
224sub create_and_lock_config {
225 my ($class, $vmid, $allow_existing) = @_;
226
227 $class->lock_config_full($vmid, 5, sub {
228 PVE::Cluster::check_vmid_unused($vmid, $allow_existing);
229
230 my $conf = eval { $class->load_config($vmid) } || {};
231 $class->check_lock($conf);
232 $conf->{lock} = 'create';
233 $class->write_config($vmid, $conf);
234 });
235}
236
1aa76f2f
TL
237# destroys configuration, only applyable for configs owned by the callers node.
238# dies if removal fails, e.g., when inquorate.
239sub destroy_config {
240 my ($class, $vmid) = @_;
241
242 my $config_fn = $class->config_file($vmid, $nodename);
243 unlink $config_fn or die "failed to remove config file: $!\n";
244}
245
58a3c91c
FG
246# Lock config file using flock, run $code with @param, unlock config file.
247# $timeout is the maximum time to aquire the flock
248# $shared eq 1 creates a non-exclusive ("read") flock
249sub lock_config_mode {
250 my ($class, $vmid, $timeout, $shared, $code, @param) = @_;
251
252 my $filename = $class->config_file_lock($vmid);
253
254 my $res = lock_file_full($filename, $timeout, $shared, $code, @param);
255
256 die $@ if $@;
257
258 return $res;
259}
260
261# Lock config file using flock, run $code with @param, unlock config file.
262sub lock_config {
263 my ($class, $vmid, $code, @param) = @_;
264
265 return $class->lock_config_full($vmid, 10, $code, @param);
266}
267
268# Checks whether the config is locked with the lock parameter
269sub check_lock {
270 my ($class, $conf) = @_;
271
272 die $class->guest_type()." is locked ($conf->{lock})\n" if $conf->{lock};
273}
274
275# Returns whether the config is locked with the lock parameter, also checks
276# whether the lock value is correct if the optional $lock is set.
277sub has_lock {
278 my ($class, $conf, $lock) = @_;
279
280 return $conf->{lock} && (!defined($lock) || $lock eq $conf->{lock});
281}
282
283# Sets the lock parameter for this VM/CT's config to $lock.
284sub set_lock {
285 my ($class, $vmid, $lock) = @_;
286
287 my $conf;
288 $class->lock_config($vmid, sub {
289 $conf = $class->load_config($vmid);
290 $class->check_lock($conf);
291 $conf->{lock} = $lock;
292 $class->write_config($vmid, $conf);
293 });
294 return $conf;
295}
296
297# Removes the lock parameter for this VM/CT's config, also checks whether
298# the lock value is correct if the optional $lock is set.
299sub remove_lock {
300 my ($class, $vmid, $lock) = @_;
301
302 $class->lock_config($vmid, sub {
303 my $conf = $class->load_config($vmid);
304 if (!$conf->{lock}) {
305 my $lockstring = defined($lock) ? "'$lock' " : "any";
306 die "no lock found trying to remove $lockstring lock\n";
307 } elsif (defined($lock) && $conf->{lock} ne $lock) {
308 die "found lock '$conf->{lock}' trying to remove '$lock' lock\n";
309 }
310 delete $conf->{lock};
311 $class->write_config($vmid, $conf);
312 });
313}
314
315# Checks whether protection mode is enabled for this VM/CT.
316sub check_protection {
317 my ($class, $conf, $err_msg) = @_;
318
319 if ($conf->{protection}) {
320 die "$err_msg - protection mode enabled\n";
321 }
322}
323
324# Adds an unused volume to $config, if possible.
325sub add_unused_volume {
326 my ($class, $config, $volid) = @_;
327
328 my $key;
329 for (my $ind = $class->__config_max_unused_disks() - 1; $ind >= 0; $ind--) {
330 my $test = "unused$ind";
331 if (my $vid = $config->{$test}) {
332 return if $vid eq $volid; # do not add duplicates
333 } else {
334 $key = $test;
335 }
336 }
337
338 die "Too many unused volumes - please delete them first.\n" if !$key;
339
340 $config->{$key} = $volid;
341
342 return $key;
343}
344
345# Returns whether the template parameter is set in $conf.
346sub is_template {
347 my ($class, $conf) = @_;
348
349 return 1 if defined $conf->{template} && $conf->{template} == 1;
350}
351
352# Checks whether $feature is availabe for the referenced volumes in $conf.
353# Note: depending on the parameters, some volumes may be skipped!
354sub has_feature {
355 my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
356 die "implement me - abstract method\n";
357}
358
3f85b14d
DM
359# get all replicatable volume (hash $res->{$volid} = 1)
360# $cleanup: for cleanup - simply ignores volumes without replicate feature
361# $norerr: never raise exceptions - return undef instead
362sub get_replicatable_volumes {
54b79ff5 363 my ($class, $storecfg, $vmid, $conf, $cleanup, $noerr) = @_;
3f85b14d
DM
364
365 die "implement me - abstract method\n";
366}
367
58a3c91c
FG
368# Internal snapshots
369
370# NOTE: Snapshot create/delete involves several non-atomic
371# actions, and can take a long time.
372# So we try to avoid locking the file and use the 'lock' variable
373# inside the config file instead.
374
375# Save the vmstate (RAM).
376sub __snapshot_save_vmstate {
377 my ($class, $vmid, $conf, $snapname, $storecfg) = @_;
378 die "implement me - abstract method\n";
379}
380
381# Check whether the VM/CT is running.
382sub __snapshot_check_running {
383 my ($class, $vmid) = @_;
384 die "implement me - abstract method\n";
385}
386
387# Check whether we need to freeze the VM/CT
388sub __snapshot_check_freeze_needed {
389 my ($sself, $vmid, $config, $save_vmstate) = @_;
390 die "implement me - abstract method\n";
391}
392
393# Freeze or unfreeze the VM/CT.
394sub __snapshot_freeze {
395 my ($class, $vmid, $unfreeze) = @_;
396
397 die "abstract method - implement me\n";
398}
399
400# Code run before and after creating all the volume snapshots
401# base: noop
402sub __snapshot_create_vol_snapshots_hook {
403 my ($class, $vmid, $snap, $running, $hook) = @_;
404
405 return;
406}
407
408# Create the volume snapshots for the VM/CT.
409sub __snapshot_create_vol_snapshot {
410 my ($class, $vmid, $vs, $volume, $snapname) = @_;
411
412 die "abstract method - implement me\n";
413}
414
415# Remove a drive from the snapshot config.
416sub __snapshot_delete_remove_drive {
417 my ($class, $snap, $drive) = @_;
418
419 die "abstract method - implement me\n";
420}
421
422# Delete the vmstate file/drive
423sub __snapshot_delete_vmstate_file {
424 my ($class, $snap, $force) = @_;
425
426 die "abstract method - implement me\n";
427}
428
429# Delete a volume snapshot
430sub __snapshot_delete_vol_snapshot {
431 my ($class, $vmid, $vs, $volume, $snapname) = @_;
432
433 die "abstract method - implement me\n";
434}
435
3fa4ce45
DC
436# called during rollback prepare, and between config rollback and starting guest
437# can change config, e.g. for vmgenid
438# $data is shared across calls and passed to vm_start
439sub __snapshot_rollback_hook {
440 my ($class, $vmid, $conf, $snap, $prepare, $data) = @_;
441
442 return;
443}
444
58a3c91c
FG
445# Checks whether a volume snapshot is possible for this volume.
446sub __snapshot_rollback_vol_possible {
447 my ($class, $volume, $snapname) = @_;
448
449 die "abstract method - implement me\n";
450}
451
452# Rolls back this volume.
453sub __snapshot_rollback_vol_rollback {
454 my ($class, $volume, $snapname) = @_;
455
456 die "abstract method - implement me\n";
457}
458
459# Stops the VM/CT for a rollback.
460sub __snapshot_rollback_vm_stop {
461 my ($class, $vmid) = @_;
462
463 die "abstract method - implement me\n";
464}
465
466# Start the VM/CT after a rollback with restored vmstate.
467sub __snapshot_rollback_vm_start {
3fa4ce45 468 my ($class, $vmid, $vmstate, $data);
58a3c91c
FG
469
470 die "abstract method - implement me\n";
471}
472
473# Get list of volume IDs which are referenced in $conf, but not in $snap.
474sub __snapshot_rollback_get_unused {
475 my ($class, $conf, $snap) = @_;
476
477 die "abstract method - implement me\n";
478}
479
480# Iterate over all configured volumes, calling $func for each key/value pair.
481sub __snapshot_foreach_volume {
482 my ($class, $conf, $func) = @_;
483
484 die "abstract method - implement me\n";
485}
486
487# Copy the current config $source to the snapshot config $dest
488sub __snapshot_copy_config {
489 my ($class, $source, $dest) = @_;
490
491 foreach my $k (keys %$source) {
492 next if $k eq 'snapshots';
493 next if $k eq 'snapstate';
494 next if $k eq 'snaptime';
495 next if $k eq 'vmstate';
496 next if $k eq 'lock';
497 next if $k eq 'digest';
498 next if $k eq 'description';
499 next if $k =~ m/^unused\d+$/;
500
501 $dest->{$k} = $source->{$k};
502 }
503};
504
505# Apply the snapshot config $snap to the config $conf (rollback)
506sub __snapshot_apply_config {
507 my ($class, $conf, $snap) = @_;
508
509 # copy snapshot list
510 my $newconf = {
511 snapshots => $conf->{snapshots},
512 };
513
eb74d483 514 # keep description and list of unused disks
58a3c91c 515 foreach my $k (keys %$conf) {
eb74d483 516 next if !($k =~ m/^unused\d+$/ || $k eq 'description');
38532f70 517
58a3c91c
FG
518 $newconf->{$k} = $conf->{$k};
519 }
520
521 $class->__snapshot_copy_config($snap, $newconf);
522
523 return $newconf;
524}
525
526# Prepares the configuration for snapshotting.
527sub __snapshot_prepare {
528 my ($class, $vmid, $snapname, $save_vmstate, $comment) = @_;
529
530 my $snap;
531
532 my $updatefn = sub {
533
534 my $conf = $class->load_config($vmid);
535
536 die "you can't take a snapshot if it's a template\n"
537 if $class->is_template($conf);
538
539 $class->check_lock($conf);
540
541 $conf->{lock} = 'snapshot';
542
543 die "snapshot name '$snapname' already used\n"
544 if defined($conf->{snapshots}->{$snapname});
545
546 my $storecfg = PVE::Storage::config();
547 die "snapshot feature is not available\n"
548 if !$class->has_feature('snapshot', $conf, $storecfg, undef, undef, $snapname eq 'vzdump');
549
550 $snap = $conf->{snapshots}->{$snapname} = {};
551
552 if ($save_vmstate && $class->__snapshot_check_running($vmid)) {
553 $class->__snapshot_save_vmstate($vmid, $conf, $snapname, $storecfg);
554 }
555
556 $class->__snapshot_copy_config($conf, $snap);
557
558 $snap->{snapstate} = "prepare";
559 $snap->{snaptime} = time();
560 $snap->{description} = $comment if $comment;
561
562 $class->write_config($vmid, $conf);
563 };
564
565 $class->lock_config($vmid, $updatefn);
566
567 return $snap;
568}
569
570# Commits the configuration after snapshotting.
571sub __snapshot_commit {
572 my ($class, $vmid, $snapname) = @_;
573
574 my $updatefn = sub {
575
576 my $conf = $class->load_config($vmid);
577
578 die "missing snapshot lock\n"
579 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
580
581 my $snap = $conf->{snapshots}->{$snapname};
582 die "snapshot '$snapname' does not exist\n" if !defined($snap);
583
584 die "wrong snapshot state\n"
585 if !($snap->{snapstate} && $snap->{snapstate} eq "prepare");
586
587 delete $snap->{snapstate};
588 delete $conf->{lock};
589
590 $conf->{parent} = $snapname;
591
592 $class->write_config($vmid, $conf);
593 };
594
595 $class->lock_config($vmid, $updatefn);
596}
597
598# Creates a snapshot for the VM/CT.
599sub snapshot_create {
600 my ($class, $vmid, $snapname, $save_vmstate, $comment) = @_;
601
602 my $snap = $class->__snapshot_prepare($vmid, $snapname, $save_vmstate, $comment);
603
604 $save_vmstate = 0 if !$snap->{vmstate};
605
606 my $conf = $class->load_config($vmid);
607
608 my ($running, $freezefs) = $class->__snapshot_check_freeze_needed($vmid, $conf, $snap->{vmstate});
609
610 my $drivehash = {};
611
612 eval {
613 if ($freezefs) {
614 $class->__snapshot_freeze($vmid, 0);
615 }
616
617 $class->__snapshot_create_vol_snapshots_hook($vmid, $snap, $running, "before");
618
619 $class->__snapshot_foreach_volume($snap, sub {
620 my ($vs, $volume) = @_;
621
622 $class->__snapshot_create_vol_snapshot($vmid, $vs, $volume, $snapname);
623 $drivehash->{$vs} = 1;
624 });
625 };
626 my $err = $@;
627
628 if ($running) {
629 $class->__snapshot_create_vol_snapshots_hook($vmid, $snap, $running, "after");
630 if ($freezefs) {
631 $class->__snapshot_freeze($vmid, 1);
632 }
633 $class->__snapshot_create_vol_snapshots_hook($vmid, $snap, $running, "after-unfreeze");
634 }
635
636 if ($err) {
637 warn "snapshot create failed: starting cleanup\n";
638 eval { $class->snapshot_delete($vmid, $snapname, 1, $drivehash); };
639 warn "$@" if $@;
640 die "$err\n";
641 }
642
643 $class->__snapshot_commit($vmid, $snapname);
644}
645
646# Deletes a snapshot.
647# Note: $drivehash is only set when called from snapshot_create.
648sub snapshot_delete {
649 my ($class, $vmid, $snapname, $force, $drivehash) = @_;
650
651 my $prepare = 1;
652
58a3c91c
FG
653 my $unused = [];
654
4c3144ea
AA
655 my $conf = $class->load_config($vmid);
656 my $snap = $conf->{snapshots}->{$snapname};
657
658 die "snapshot '$snapname' does not exist\n" if !defined($snap);
659
58a3c91c
FG
660 $class->set_lock($vmid, 'snapshot-delete')
661 if (!$drivehash); # doesn't already have a 'snapshot' lock
662
663 my $unlink_parent = sub {
664 my ($confref, $new_parent) = @_;
665
666 if ($confref->{parent} && $confref->{parent} eq $snapname) {
667 if ($new_parent) {
668 $confref->{parent} = $new_parent;
669 } else {
670 delete $confref->{parent};
671 }
672 }
673 };
674
675 my $remove_drive = sub {
676 my ($drive) = @_;
677
678 my $conf = $class->load_config($vmid);
679 $snap = $conf->{snapshots}->{$snapname};
680 die "snapshot '$snapname' does not exist\n" if !defined($snap);
681
682 $class->__snapshot_delete_remove_drive($snap, $drive);
683
684 $class->write_config($vmid, $conf);
685 };
686
687 #prepare
688 $class->lock_config($vmid, sub {
689 my $conf = $class->load_config($vmid);
690
691 die "you can't delete a snapshot if vm is a template\n"
692 if $class->is_template($conf);
693
694 $snap = $conf->{snapshots}->{$snapname};
695 die "snapshot '$snapname' does not exist\n" if !defined($snap);
696
697 $snap->{snapstate} = 'delete';
698
699 $class->write_config($vmid, $conf);
700 });
701
702 # now remove vmstate file
703 if ($snap->{vmstate}) {
704 $class->__snapshot_delete_vmstate_file($snap, $force);
705
706 # save changes (remove vmstate from snapshot)
707 $class->lock_config($vmid, $remove_drive, 'vmstate') if !$force;
708 };
709
710 # now remove all volume snapshots
711 $class->__snapshot_foreach_volume($snap, sub {
712 my ($vs, $volume) = @_;
713
714 return if $snapname eq 'vzdump' && $vs ne 'rootfs' && !$volume->{backup};
715 if (!$drivehash || $drivehash->{$vs}) {
716 eval { $class->__snapshot_delete_vol_snapshot($vmid, $vs, $volume, $snapname, $unused); };
717 if (my $err = $@) {
718 die $err if !$force;
719 warn $err;
720 }
721 }
722
723 # save changes (remove drive from snapshot)
724 $class->lock_config($vmid, $remove_drive, $vs) if !$force;
725 });
726
727 # now cleanup config
728 $class->lock_config($vmid, sub {
729 my $conf = $class->load_config($vmid);
730 $snap = $conf->{snapshots}->{$snapname};
731 die "snapshot '$snapname' does not exist\n" if !defined($snap);
732
733 # remove parent refs
734 &$unlink_parent($conf, $snap->{parent});
735 foreach my $sn (keys %{$conf->{snapshots}}) {
736 next if $sn eq $snapname;
737 &$unlink_parent($conf->{snapshots}->{$sn}, $snap->{parent});
738 }
739
740
741 delete $conf->{snapshots}->{$snapname};
742 delete $conf->{lock};
743 foreach my $volid (@$unused) {
744 $class->add_unused_volume($conf, $volid);
745 }
746
747 $class->write_config($vmid, $conf);
748 });
749}
750
751# Rolls back to a given snapshot.
752sub snapshot_rollback {
753 my ($class, $vmid, $snapname) = @_;
754
755 my $prepare = 1;
756
757 my $storecfg = PVE::Storage::config();
758
759 my $conf = $class->load_config($vmid);
760
761 my $get_snapshot_config = sub {
762
763 die "you can't rollback if vm is a template\n" if $class->is_template($conf);
764
765 my $res = $conf->{snapshots}->{$snapname};
766
767 die "snapshot '$snapname' does not exist\n" if !defined($res);
768
769 return $res;
770 };
771
683a30e0
WL
772 my $repl_conf = PVE::ReplicationConfig->new();
773 if ($repl_conf->check_for_existing_jobs($vmid, 1)) {
774 # remove all replication snapshots
c324e907 775 my $volumes = $class->get_replicatable_volumes($storecfg, $vmid, $conf, 1);
683a30e0
WL
776 my $sorted_volids = [ sort keys %$volumes ];
777
778 # remove all local replication snapshots (jobid => undef)
779 my $logfunc = sub { my $line = shift; chomp $line; print "$line\n"; };
780 PVE::Replication::prepare($storecfg, $sorted_volids, undef, 0, undef, $logfunc);
781 }
782
58a3c91c
FG
783 my $snap = &$get_snapshot_config();
784
785 $class->__snapshot_foreach_volume($snap, sub {
786 my ($vs, $volume) = @_;
787
788 $class->__snapshot_rollback_vol_possible($volume, $snapname);
789 });
790
3fa4ce45
DC
791 my $data = {};
792
58a3c91c
FG
793 my $updatefn = sub {
794
795 $conf = $class->load_config($vmid);
796
797 $snap = &$get_snapshot_config();
798
799 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
800 if $snap->{snapstate};
801
802 if ($prepare) {
803 $class->check_lock($conf);
804 $class->__snapshot_rollback_vm_stop($vmid);
805 }
806
807 die "unable to rollback vm $vmid: vm is running\n"
808 if $class->__snapshot_check_running($vmid);
809
810 if ($prepare) {
811 $conf->{lock} = 'rollback';
812 } else {
813 die "got wrong lock\n" if !($conf->{lock} && $conf->{lock} eq 'rollback');
814 delete $conf->{lock};
58a3c91c 815
58a3c91c
FG
816 my $unused = $class->__snapshot_rollback_get_unused($conf, $snap);
817
818 foreach my $volid (@$unused) {
819 $class->add_unused_volume($conf, $volid);
820 }
821
58a3c91c
FG
822 # copy snapshot config to current config
823 $conf = $class->__snapshot_apply_config($conf, $snap);
824 $conf->{parent} = $snapname;
58a3c91c
FG
825 }
826
3fa4ce45
DC
827 $class->__snapshot_rollback_hook($vmid, $conf, $snap, $prepare, $data);
828
58a3c91c
FG
829 $class->write_config($vmid, $conf);
830
831 if (!$prepare && $snap->{vmstate}) {
3fa4ce45 832 $class->__snapshot_rollback_vm_start($vmid, $snap->{vmstate}, $data);
58a3c91c
FG
833 }
834 };
835
836 $class->lock_config($vmid, $updatefn);
837
838 $class->__snapshot_foreach_volume($snap, sub {
839 my ($vs, $volume) = @_;
840
841 $class->__snapshot_rollback_vol_rollback($volume, $snapname);
842 });
843
844 $prepare = 0;
845 $class->lock_config($vmid, $updatefn);
846}
847
eb50bb61
RV
848# bash completion helper
849
90fba9cd
WB
850sub snapshot_list {
851 my ($class, $vmid) = @_;
eb50bb61 852
90fba9cd
WB
853 my $snapshot = eval {
854 my $conf = $class->load_config($vmid);
855 my $snapshots = $conf->{snapshots} || [];
856 [ sort keys %$snapshots ]
857 } || [];
eb50bb61
RV
858
859 return $snapshot;
860}
861
58a3c91c 8621;