Introduce __snapshot_create_vol_snapshots_hook
[pve-common.git] / src / PVE / AbstractConfig.pm
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}) {
139             die "no lock found trying to remove lock '$lock'\n";
140         } elsif (defined($lock) && $conf->{lock} ne $lock) {
141             die "found lock '$conf->{lock}' trying to remove lock '$lock'\n";
142         }
143         delete $conf->{lock};
144         $class->write_config($vmid, $conf);
145     });
146 }
147
148 # Checks whether protection mode is enabled for this VM/CT.
149 sub check_protection {
150     my ($class, $conf, $err_msg) = @_;
151
152     if ($conf->{protection}) {
153         die "$err_msg - protection mode enabled\n";
154     }
155 }
156
157 # Adds an unused volume to $config, if possible.
158 sub add_unused_volume {
159     my ($class, $config, $volid) = @_;
160
161     my $key;
162     for (my $ind = $class->__config_max_unused_disks() - 1; $ind >= 0; $ind--) {
163         my $test = "unused$ind";
164         if (my $vid = $config->{$test}) {
165             return if $vid eq $volid; # do not add duplicates
166         } else {
167             $key = $test;
168         }
169     }
170
171     die "Too many unused volumes - please delete them first.\n" if !$key;
172
173     $config->{$key} = $volid;
174
175     return $key;
176 }
177
178 # Returns whether the template parameter is set in $conf.
179 sub is_template {
180     my ($class, $conf) = @_;
181
182     return 1 if defined $conf->{template} && $conf->{template} == 1;
183 }
184
185 # Checks whether $feature is availabe for the referenced volumes in $conf.
186 # Note: depending on the parameters, some volumes may be skipped!
187 sub has_feature {
188     my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
189     die "implement me - abstract method\n";
190 }
191
192 # Internal snapshots
193
194 # NOTE: Snapshot create/delete involves several non-atomic
195 # actions, and can take a long time.
196 # So we try to avoid locking the file and use the 'lock' variable
197 # inside the config file instead.
198
199 # Save the vmstate (RAM).
200 sub __snapshot_save_vmstate {
201     my ($class, $vmid, $conf, $snapname, $storecfg) = @_;
202     die "implement me - abstract method\n";
203 }
204
205 # Check whether the VM/CT is running.
206 sub __snapshot_check_running {
207     my ($class, $vmid) = @_;
208     die "implement me - abstract method\n";
209 }
210
211 # Check whether we need to freeze the VM/CT
212 sub __snapshot_check_freeze_needed {
213     my ($sself, $vmid, $config, $save_vmstate) = @_;
214     die "implement me - abstract method\n";
215 }
216
217 # Freeze or unfreeze the VM/CT.
218 sub __snapshot_freeze {
219     my ($class, $vmid, $unfreeze) = @_;
220
221     die "abstract method - implement me\n";
222 }
223
224 # Code run before and after creating all the volume snapshots
225 # base: noop
226 sub __snapshot_create_vol_snapshots_hook {
227     my ($class, $vmid, $snap, $running, $hook) = @_;
228
229     return;
230 }
231
232 # Create the volume snapshots for the VM/CT.
233 sub __snapshot_create_vol_snapshot {
234     my ($class, $vmid, $vs, $volume, $snapname) = @_;
235
236     die "abstract method - implement me\n";
237 }
238
239 # Remove a drive from the snapshot config.
240 sub __snapshot_delete_remove_drive {
241     my ($class, $snap, $remove_drive) = @_;
242
243     die "abstract method - implement me\n";
244 }
245
246 # Delete the vmstate file/drive
247 sub __snapshot_delete_vmstate_file {
248     my ($class, $snap, $force) = @_;
249
250     die "abstract method - implement me\n";
251 }
252
253 # Delete a volume snapshot
254 sub __snapshot_delete_vol_snapshot {
255     my ($class, $vmid, $vs, $volume, $snapname) = @_;
256
257     die "abstract method - implement me\n";
258 }
259
260 # Checks whether a volume snapshot is possible for this volume.
261 sub __snapshot_rollback_vol_possible {
262     my ($class, $volume, $snapname) = @_;
263
264     die "abstract method - implement me\n";
265 }
266
267 # Rolls back this volume.
268 sub __snapshot_rollback_vol_rollback {
269     my ($class, $volume, $snapname) = @_;
270
271     die "abstract method - implement me\n";
272 }
273
274 # Stops the VM/CT for a rollback.
275 sub __snapshot_rollback_vm_stop {
276     my ($class, $vmid) = @_;
277
278     die "abstract method - implement me\n";
279 }
280
281 # Start the VM/CT after a rollback with restored vmstate.
282 sub __snapshot_rollback_vm_start {
283     my ($class, $vmid, $vmstate, $forcemachine);
284
285     die "abstract method - implement me\n";
286 }
287
288 # Iterate over all configured volumes, calling $func for each key/value pair.
289 sub __snapshot_foreach_volume {
290     my ($class, $conf, $func) = @_;
291
292     die "abstract method - implement me\n";
293 }
294
295 # Copy the current config $source to the snapshot config $dest
296 sub __snapshot_copy_config {
297     my ($class, $source, $dest) = @_;
298
299     foreach my $k (keys %$source) {
300         next if $k eq 'snapshots';
301         next if $k eq 'snapstate';
302         next if $k eq 'snaptime';
303         next if $k eq 'vmstate';
304         next if $k eq 'lock';
305         next if $k eq 'digest';
306         next if $k eq 'description';
307         next if $k =~ m/^unused\d+$/;
308
309         $dest->{$k} = $source->{$k};
310     }
311 };
312
313 # Apply the snapshot config $snap to the config $conf (rollback)
314 sub __snapshot_apply_config {
315     my ($class, $conf, $snap) = @_;
316
317     # copy snapshot list
318     my $newconf = {
319         snapshots => $conf->{snapshots},
320     };
321
322     # keep description and list of unused disks
323     foreach my $k (keys %$conf) {
324         next if !($k =~ m/^unused\d+$/ || $k eq 'description');
325         $newconf->{$k} = $conf->{$k};
326     }
327
328     $class->__snapshot_copy_config($snap, $newconf);
329
330     return $newconf;
331 }
332
333 # Prepares the configuration for snapshotting.
334 sub __snapshot_prepare {
335     my ($class, $vmid, $snapname, $save_vmstate, $comment) = @_;
336
337     my $snap;
338
339     my $updatefn =  sub {
340
341         my $conf = $class->load_config($vmid);
342
343         die "you can't take a snapshot if it's a template\n"
344             if $class->is_template($conf);
345
346         $class->check_lock($conf);
347
348         $conf->{lock} = 'snapshot';
349
350         die "snapshot name '$snapname' already used\n"
351             if defined($conf->{snapshots}->{$snapname});
352
353         my $storecfg = PVE::Storage::config();
354         die "snapshot feature is not available\n"
355             if !$class->has_feature('snapshot', $conf, $storecfg, undef, undef, $snapname eq 'vzdump');
356
357         $snap = $conf->{snapshots}->{$snapname} = {};
358
359         if ($save_vmstate && $class->__snapshot_check_running($vmid)) {
360             $class->__snapshot_save_vmstate($vmid, $conf, $snapname, $storecfg);
361         }
362
363         $class->__snapshot_copy_config($conf, $snap);
364
365         $snap->{snapstate} = "prepare";
366         $snap->{snaptime} = time();
367         $snap->{description} = $comment if $comment;
368
369         $class->write_config($vmid, $conf);
370     };
371
372     $class->lock_config($vmid, $updatefn);
373
374     return $snap;
375 }
376
377 # Commits the configuration after snapshotting.
378 sub __snapshot_commit {
379     my ($class, $vmid, $snapname) = @_;
380
381     my $updatefn = sub {
382
383         my $conf = $class->load_config($vmid);
384
385         die "missing snapshot lock\n"
386             if !($conf->{lock} && $conf->{lock} eq 'snapshot');
387
388         my $snap = $conf->{snapshots}->{$snapname};
389         die "snapshot '$snapname' does not exist\n" if !defined($snap);
390
391         die "wrong snapshot state\n"
392             if !($snap->{snapstate} && $snap->{snapstate} eq "prepare");
393
394         delete $snap->{snapstate};
395         delete $conf->{lock};
396
397         $conf->{parent} = $snapname;
398
399         $class->write_config($vmid, $conf);
400     };
401
402     $class->lock_config($vmid, $updatefn);
403 }
404
405 # Creates a snapshot for the VM/CT.
406 sub snapshot_create {
407     my ($class, $vmid, $snapname, $save_vmstate, $comment) = @_;
408
409     my $snap = $class->__snapshot_prepare($vmid, $snapname, $save_vmstate, $comment);
410
411     $save_vmstate = 0 if !$snap->{vmstate};
412
413     my $conf = $class->load_config($vmid);
414
415     my ($running, $freezefs) = $class->__snapshot_check_freeze_needed($vmid, $conf, $snap->{vmstate});
416
417     my $drivehash = {};
418
419     eval {
420         if ($freezefs) {
421             $class->__snapshot_freeze($vmid, 0);
422         }
423
424         $class->__snapshot_create_vol_snapshots_hook($vmid, $snap, $running, "before");
425
426         $class->__snapshot_foreach_volume($snap, sub {
427             my ($vs, $volume) = @_;
428
429             $class->__snapshot_create_vol_snapshot($vmid, $vs, $volume, $snapname);
430             $drivehash->{$vs} = 1;
431         });
432     };
433     my $err = $@;
434
435     if ($running) {
436         $class->__snapshot_create_vol_snapshots_hook($vmid, $snap, $running, "after");
437         if ($freezefs) {
438             $class->__snapshot_freeze($vmid, 1);
439         }
440         $class->__snapshot_create_vol_snapshots_hook($vmid, $snap, $running, "after-unfreeze");
441     }
442
443     if ($err) {
444         warn "snapshot create failed: starting cleanup\n";
445         eval { $class->snapshot_delete($vmid, $snapname, 1, $drivehash); };
446         warn "$@" if $@;
447         die "$err\n";
448     }
449
450     $class->__snapshot_commit($vmid, $snapname);
451 }
452
453 # Deletes a snapshot.
454 # Note: $drivehash is only set when called from snapshot_create.
455 sub snapshot_delete {
456     my ($class, $vmid, $snapname, $force, $drivehash) = @_;
457
458     my $prepare = 1;
459
460     my $snap;
461     my $unused = [];
462
463     my $unlink_parent = sub {
464         my ($confref, $new_parent) = @_;
465
466         if ($confref->{parent} && $confref->{parent} eq $snapname) {
467             if ($new_parent) {
468                 $confref->{parent} = $new_parent;
469             } else {
470                 delete $confref->{parent};
471             }
472         }
473     };
474
475     my $updatefn =  sub {
476         my ($remove_drive) = @_;
477
478         my $conf = $class->load_config($vmid);
479
480         if (!$drivehash) {
481             $class->check_lock($conf);
482             die "you can't delete a snapshot if vm is a template\n"
483                 if $class->is_template($conf);
484         }
485
486         $snap = $conf->{snapshots}->{$snapname};
487
488         die "snapshot '$snapname' does not exist\n" if !defined($snap);
489
490         # remove parent refs
491         if (!$prepare) {
492             &$unlink_parent($conf, $snap->{parent});
493             foreach my $sn (keys %{$conf->{snapshots}}) {
494                 next if $sn eq $snapname;
495                 &$unlink_parent($conf->{snapshots}->{$sn}, $snap->{parent});
496             }
497         }
498
499         if ($remove_drive) {
500             $class->__snapshot_delete_remove_drive($snap, $remove_drive);
501         }
502
503         if ($prepare) {
504             $snap->{snapstate} = 'delete';
505         } else {
506             delete $conf->{snapshots}->{$snapname};
507             delete $conf->{lock} if $drivehash;
508             foreach my $volid (@$unused) {
509                 $class->add_unused_volume($conf, $volid);
510             }
511         }
512
513         $class->write_config($vmid, $conf);
514     };
515
516     $class->lock_config($vmid, $updatefn);
517
518     # now remove vmstate file
519     if ($snap->{vmstate}) {
520         $class->__snapshot_delete_vmstate_file($snap, $force);
521
522         # save changes (remove vmstate from snapshot)
523         $class->lock_config($vmid, $updatefn, 'vmstate') if !$force;
524     };
525
526     # now remove all volume snapshots
527     $class->__snapshot_foreach_volume($snap, sub {
528         my ($vs, $volume) = @_;
529
530         return if $snapname eq 'vzdump' && $vs ne 'rootfs' && !$volume->{backup};
531         if (!$drivehash || $drivehash->{$vs}) {
532             eval { $class->__snapshot_delete_vol_snapshot($vmid, $vs, $volume, $snapname); };
533             if (my $err = $@) {
534                 die $err if !$force;
535                 warn $err;
536             }
537         }
538
539         # save changes (remove mp from snapshot)
540         $class->lock_config($vmid, $updatefn, $vs) if !$force;
541         push @$unused, $volume->{volume};
542     });
543
544     # now cleanup config
545     $prepare = 0;
546     $class->lock_config($vmid, $updatefn);
547 }
548
549 # Rolls back to a given snapshot.
550 sub snapshot_rollback {
551     my ($class, $vmid, $snapname) = @_;
552
553     my $prepare = 1;
554
555     my $storecfg = PVE::Storage::config();
556
557     my $conf = $class->load_config($vmid);
558
559     my $get_snapshot_config = sub {
560
561         die "you can't rollback if vm is a template\n" if $class->is_template($conf);
562
563         my $res = $conf->{snapshots}->{$snapname};
564
565         die "snapshot '$snapname' does not exist\n" if !defined($res);
566
567         return $res;
568     };
569
570     my $snap = &$get_snapshot_config();
571
572     $class->__snapshot_foreach_volume($snap, sub {
573         my ($vs, $volume) = @_;
574
575         $class->__snapshot_rollback_vol_possible($volume, $snapname);
576     });
577
578     my $updatefn = sub {
579
580         $conf = $class->load_config($vmid);
581
582         $snap = &$get_snapshot_config();
583
584         die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
585             if $snap->{snapstate};
586
587         if ($prepare) {
588             $class->check_lock($conf);
589             $class->__snapshot_rollback_vm_stop($vmid);
590         }
591
592         die "unable to rollback vm $vmid: vm is running\n"
593             if $class->__snapshot_check_running($vmid);
594
595         if ($prepare) {
596             $conf->{lock} = 'rollback';
597         } else {
598             die "got wrong lock\n" if !($conf->{lock} && $conf->{lock} eq 'rollback');
599             delete $conf->{lock};
600         }
601
602         # machine only relevant for Qemu
603         my $forcemachine;
604
605         if (!$prepare) {
606             my $has_machine_config = defined($conf->{machine});
607
608             # copy snapshot config to current config
609             $conf = $class->__snapshot_apply_config($conf, $snap);
610             $conf->{parent} = $snapname;
611
612             # Note: old code did not store 'machine', so we try to be smart
613             # and guess the snapshot was generated with kvm 1.4 (pc-i440fx-1.4).
614             $forcemachine = $conf->{machine} || 'pc-i440fx-1.4';
615             # we remove the 'machine' configuration if not explicitly specified
616             # in the original config.
617             delete $conf->{machine} if $snap->{vmstate} && !$has_machine_config;
618         }
619
620         $class->write_config($vmid, $conf);
621
622         if (!$prepare && $snap->{vmstate}) {
623             $class->__snapshot_rollback_vm_start($vmid, $snap->{vmstate}, $forcemachine);
624         }
625     };
626
627     $class->lock_config($vmid, $updatefn);
628
629     $class->__snapshot_foreach_volume($snap, sub {
630         my ($vs, $volume) = @_;
631
632         $class->__snapshot_rollback_vol_rollback($volume, $snapname);
633     });
634
635     $prepare = 0;
636     $class->lock_config($vmid, $updatefn);
637 }
638
639 1;