]> git.proxmox.com Git - pve-common.git/blob - src/PVE/AbstractConfig.pm
277434d04cf0d30acbc84793f462df51a46ba8b2
[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;