]>
Commit | Line | Data |
---|---|---|
58a3c91c 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 | use PVE::Cluster; | |
9 | use PVE::Storage; | |
10 | ||
14f17b49 DM |
11 | use PVE::ReplicationConfig; |
12 | use PVE::Replication; | |
13 | ||
58a3c91c FG |
14 | my $nodename = PVE::INotify::nodename(); |
15 | ||
16 | # Printable string, currently either "VM" or "CT" | |
17 | sub guest_type { | |
18 | my ($class) = @_; | |
19 | die "abstract method - implement me "; | |
20 | } | |
21 | ||
22 | sub __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 | |
29 | sub 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 | |
35 | sub 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 | |
41 | sub 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 | |
49 | sub 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 | |
63 | sub 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 | ||
73 | sub 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 | ||
93 | sub 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 | ||
105 | sub 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 | ||
119 | sub 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 | ||
138 | sub 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 |
170 | sub 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 | ||
184 | sub 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 | |
212 | sub 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 |
224 | sub 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. | |
239 | sub 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 | |
249 | sub 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. | |
262 | sub 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 | |
269 | sub 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. | |
277 | sub 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. | |
284 | sub 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. | |
299 | sub 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. | |
316 | sub 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. | |
325 | sub 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. | |
346 | sub 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! | |
354 | sub 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 | |
362 | sub 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). | |
376 | sub __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. | |
382 | sub __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 | |
388 | sub __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. | |
394 | sub __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 | |
402 | sub __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. | |
409 | sub __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. | |
416 | sub __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 | |
423 | sub __snapshot_delete_vmstate_file { | |
424 | my ($class, $snap, $force) = @_; | |
425 | ||
426 | die "abstract method - implement me\n"; | |
427 | } | |
428 | ||
429 | # Delete a volume snapshot | |
430 | sub __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 | |
439 | sub __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. |
446 | sub __snapshot_rollback_vol_possible { | |
447 | my ($class, $volume, $snapname) = @_; | |
448 | ||
449 | die "abstract method - implement me\n"; | |
450 | } | |
451 | ||
452 | # Rolls back this volume. | |
453 | sub __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. | |
460 | sub __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. | |
467 | sub __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. | |
474 | sub __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. | |
481 | sub __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 | |
488 | sub __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) | |
506 | sub __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. | |
527 | sub __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. | |
571 | sub __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. | |
599 | sub 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. | |
648 | sub 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. | |
752 | sub 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 |
850 | sub 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 | 862 | 1; |