23512d0ae924a7e90f0385936ba1b3a3b6149bdb
[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             my $lockstring = defined($lock) ? "'$lock' " : "any";
140             die "no lock found trying to remove $lockstring lock\n";
141         } elsif (defined($lock) && $conf->{lock} ne $lock) {
142             die "found lock '$conf->{lock}' trying to remove '$lock' lock\n";
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
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
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
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
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
432         $class->__snapshot_create_vol_snapshots_hook($vmid, $snap, $running, "before");
433
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) {
444         $class->__snapshot_create_vol_snapshots_hook($vmid, $snap, $running, "after");
445         if ($freezefs) {
446             $class->__snapshot_freeze($vmid, 1);
447         }
448         $class->__snapshot_create_vol_snapshots_hook($vmid, $snap, $running, "after-unfreeze");
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     $class->set_lock($vmid, 'snapshot-delete')
472         if (!$drivehash); # doesn't already have a 'snapshot' lock
473
474     my $unlink_parent = sub {
475         my ($confref, $new_parent) = @_;
476
477         if ($confref->{parent} && $confref->{parent} eq $snapname) {
478             if ($new_parent) {
479                 $confref->{parent} = $new_parent;
480             } else {
481                 delete $confref->{parent};
482             }
483         }
484     };
485
486     my $updatefn =  sub {
487         my ($remove_drive) = @_;
488
489         my $conf = $class->load_config($vmid);
490
491         if (!$drivehash) {
492             die "you can't delete a snapshot if vm is a template\n"
493                 if $class->is_template($conf);
494         }
495
496         $snap = $conf->{snapshots}->{$snapname};
497
498         die "snapshot '$snapname' does not exist\n" if !defined($snap);
499
500         # remove parent refs
501         if (!$prepare) {
502             &$unlink_parent($conf, $snap->{parent});
503             foreach my $sn (keys %{$conf->{snapshots}}) {
504                 next if $sn eq $snapname;
505                 &$unlink_parent($conf->{snapshots}->{$sn}, $snap->{parent});
506             }
507         }
508
509         if ($remove_drive) {
510             $class->__snapshot_delete_remove_drive($snap, $remove_drive);
511         }
512
513         if ($prepare) {
514             $snap->{snapstate} = 'delete';
515         } else {
516             delete $conf->{snapshots}->{$snapname};
517             delete $conf->{lock};
518             foreach my $volid (@$unused) {
519                 $class->add_unused_volume($conf, $volid);
520             }
521         }
522
523         $class->write_config($vmid, $conf);
524     };
525
526     $class->lock_config($vmid, $updatefn);
527
528     # now remove vmstate file
529     if ($snap->{vmstate}) {
530         $class->__snapshot_delete_vmstate_file($snap, $force);
531
532         # save changes (remove vmstate from snapshot)
533         $class->lock_config($vmid, $updatefn, 'vmstate') if !$force;
534     };
535
536     # now remove all volume snapshots
537     $class->__snapshot_foreach_volume($snap, sub {
538         my ($vs, $volume) = @_;
539
540         return if $snapname eq 'vzdump' && $vs ne 'rootfs' && !$volume->{backup};
541         if (!$drivehash || $drivehash->{$vs}) {
542             eval { $class->__snapshot_delete_vol_snapshot($vmid, $vs, $volume, $snapname, $unused); };
543             if (my $err = $@) {
544                 die $err if !$force;
545                 warn $err;
546             }
547         }
548
549         # save changes (remove mp from snapshot)
550         $class->lock_config($vmid, $updatefn, $vs) if !$force;
551     });
552
553     # now cleanup config
554     $prepare = 0;
555     $class->lock_config($vmid, $updatefn);
556 }
557
558 # Rolls back to a given snapshot.
559 sub snapshot_rollback {
560     my ($class, $vmid, $snapname) = @_;
561
562     my $prepare = 1;
563
564     my $storecfg = PVE::Storage::config();
565
566     my $conf = $class->load_config($vmid);
567
568     my $get_snapshot_config = sub {
569
570         die "you can't rollback if vm is a template\n" if $class->is_template($conf);
571
572         my $res = $conf->{snapshots}->{$snapname};
573
574         die "snapshot '$snapname' does not exist\n" if !defined($res);
575
576         return $res;
577     };
578
579     my $snap = &$get_snapshot_config();
580
581     $class->__snapshot_foreach_volume($snap, sub {
582         my ($vs, $volume) = @_;
583
584         $class->__snapshot_rollback_vol_possible($volume, $snapname);
585     });
586
587     my $updatefn = sub {
588
589         $conf = $class->load_config($vmid);
590
591         $snap = &$get_snapshot_config();
592
593         die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
594             if $snap->{snapstate};
595
596         if ($prepare) {
597             $class->check_lock($conf);
598             $class->__snapshot_rollback_vm_stop($vmid);
599         }
600
601         die "unable to rollback vm $vmid: vm is running\n"
602             if $class->__snapshot_check_running($vmid);
603
604         if ($prepare) {
605             $conf->{lock} = 'rollback';
606         } else {
607             die "got wrong lock\n" if !($conf->{lock} && $conf->{lock} eq 'rollback');
608             delete $conf->{lock};
609         }
610
611         # machine only relevant for Qemu
612         my $forcemachine;
613
614         if (!$prepare) {
615             my $unused = $class->__snapshot_rollback_get_unused($conf, $snap);
616
617             foreach my $volid (@$unused) {
618                 $class->add_unused_volume($conf, $volid);
619             }
620
621             my $has_machine_config = defined($conf->{machine});
622
623             # copy snapshot config to current config
624             $conf = $class->__snapshot_apply_config($conf, $snap);
625             $conf->{parent} = $snapname;
626
627             # Note: old code did not store 'machine', so we try to be smart
628             # and guess the snapshot was generated with kvm 1.4 (pc-i440fx-1.4).
629             $forcemachine = $conf->{machine} || 'pc-i440fx-1.4';
630             # we remove the 'machine' configuration if not explicitly specified
631             # in the original config.
632             delete $conf->{machine} if $snap->{vmstate} && !$has_machine_config;
633         }
634
635         $class->write_config($vmid, $conf);
636
637         if (!$prepare && $snap->{vmstate}) {
638             $class->__snapshot_rollback_vm_start($vmid, $snap->{vmstate}, $forcemachine);
639         }
640     };
641
642     $class->lock_config($vmid, $updatefn);
643
644     $class->__snapshot_foreach_volume($snap, sub {
645         my ($vs, $volume) = @_;
646
647         $class->__snapshot_rollback_vol_rollback($volume, $snapname);
648     });
649
650     $prepare = 0;
651     $class->lock_config($vmid, $updatefn);
652 }
653
654 1;