]> git.proxmox.com Git - pve-guest-common.git/blobdiff - PVE/AbstractConfig.pm
refactor/cleanup add_to_pending_delete sligthly
[pve-guest-common.git] / PVE / AbstractConfig.pm
index 482f0e28c8bd99333da83e054ce5bd4631c30fa1..b07ef5936131c95dbf517ac9c683b9c6a0c6b69b 100644 (file)
@@ -8,6 +8,9 @@ use PVE::INotify;
 use PVE::Cluster;
 use PVE::Storage;
 
+use PVE::ReplicationConfig;
+use PVE::Replication;
+
 my $nodename = PVE::INotify::nodename();
 
 # Printable string, currently either "VM" or "CT"
@@ -65,6 +68,139 @@ sub write_config {
     PVE::Cluster::cfs_write_file($cfspath, $conf);
 }
 
+# Pending changes related
+
+sub parse_pending_delete {
+    my ($class, $data) = @_;
+
+    return {} if !$data;
+
+    $data =~ s/[,;]/ /g;
+    $data =~ s/^\s+//;
+
+    my $pending_deletions = {};
+    for my $entry (split(/\s+/, $data)) {
+       my ($force, $key) = $entry =~ /^(!?)(.*)$/;
+
+       $pending_deletions->{$key} = {
+           force => $force ? 1 : 0,
+       };
+    }
+
+    return $pending_deletions;
+}
+
+sub print_pending_delete {
+    my ($class, $delete_hash) = @_;
+
+    my $render_key = sub {
+       my $key = shift;
+       $key = "!$key" if $delete_hash->{$key}->{force};
+       return $key;
+    };
+
+    join (',', map { $render_key->($_) } sort keys %$delete_hash);
+}
+
+sub add_to_pending_delete {
+    my ($class, $conf, $key, $force) = @_;
+
+    $conf->{pending} //= {};
+    my $pending = $conf->{pending};
+    my $pending_delete_hash = $class->parse_pending_delete($pending->{delete});
+
+    $pending_delete_hash->{$key} = { force => $force };
+
+    $pending->{delete} = $class->print_pending_delete($pending_delete_hash);
+
+    return $conf;
+}
+
+sub remove_from_pending_delete {
+    my ($class, $conf, $key) = @_;
+
+    my $pending_delete_hash = $class->parse_pending_delete($conf->{pending}->{delete});
+    delete $pending_delete_hash->{$key};
+
+    if (%$pending_delete_hash) {
+       $conf->{pending}->{delete} = $class->print_pending_delete($pending_delete_hash);
+    } else {
+       delete $conf->{pending}->{delete};
+    }
+}
+
+sub cleanup_pending {
+    my ($class, $conf) = @_;
+
+    # remove pending changes when nothing changed
+    my $changes;
+    foreach my $opt (keys %{$conf->{pending}}) {
+       next if $opt eq 'delete'; # just to be sure
+       if (defined($conf->{$opt}) && ($conf->{pending}->{$opt} eq  $conf->{$opt})) {
+           $changes = 1;
+           delete $conf->{pending}->{$opt};
+       }
+    }
+
+    my $current_delete_hash = $class->parse_pending_delete($conf->{pending}->{delete});
+    my $pending_delete_hash = {};
+    while (my ($opt, $force) = each %$current_delete_hash) {
+       if (defined($conf->{$opt})) {
+           $pending_delete_hash->{$opt} = $force;
+       } else {
+           $changes = 1;
+       }
+    }
+
+    if (%$pending_delete_hash) {
+       $conf->{pending}->{delete} = $class->print_pending_delete($pending_delete_hash);
+    } else {
+       delete $conf->{pending}->{delete};
+    }
+
+    return $changes;
+}
+
+sub load_snapshot_config {
+    my ($class, $vmid, $snapname) = @_;
+
+    my $conf = $class->load_config($vmid);
+
+    my $snapshot = $conf->{snapshots}->{$snapname};
+    die "snapshot '$snapname' does not exist\n" if !defined($snapshot);
+
+    $snapshot->{digest} = $conf->{digest};
+
+    return $snapshot;
+
+}
+
+sub load_current_config {
+    my ($class, $vmid, $current) = @_;
+
+    my $conf = $class->load_config($vmid);
+
+    # take pending changes in
+    if (!$current) {
+       foreach my $opt (keys %{$conf->{pending}}) {
+           next if $opt eq 'delete';
+           my $value = $conf->{pending}->{$opt};
+           next if ref($value); # just to be sure
+           $conf->{$opt} = $value;
+       }
+       my $pending_delete_hash = $class->parse_pending_delete($conf->{pending}->{delete});
+       foreach my $opt (keys %$pending_delete_hash) {
+           delete $conf->{$opt} if $conf->{$opt};
+       }
+    }
+
+    delete $conf->{snapshots};
+    delete $conf->{pending};
+
+    return $conf;
+}
+
+
 # Lock config file using flock, run $code with @param, unlock config file.
 # $timeout is the maximum time to aquire the flock
 sub lock_config_full {
@@ -79,6 +215,28 @@ sub lock_config_full {
     return $res;
 }
 
+sub create_and_lock_config {
+    my ($class, $vmid, $allow_existing) = @_;
+
+    $class->lock_config_full($vmid, 5, sub {
+       PVE::Cluster::check_vmid_unused($vmid, $allow_existing);
+
+       my $conf = eval { $class->load_config($vmid) } || {};
+       $class->check_lock($conf);
+       $conf->{lock} = 'create';
+       $class->write_config($vmid, $conf);
+    });
+}
+
+# destroys configuration, only applyable for configs owned by the callers node.
+# dies if removal fails, e.g., when inquorate.
+sub destroy_config {
+    my ($class, $vmid) = @_;
+
+    my $config_fn = $class->config_file($vmid, $nodename);
+    unlink $config_fn or die "failed to remove config file: $!\n";
+}
+
 # Lock config file using flock, run $code with @param, unlock config file.
 # $timeout is the maximum time to aquire the flock
 # $shared eq 1 creates a non-exclusive ("read") flock
@@ -192,6 +350,15 @@ sub has_feature {
     die "implement me - abstract method\n";
 }
 
+# get all replicatable volume (hash $res->{$volid} = 1)
+# $cleanup: for cleanup - simply ignores volumes without replicate feature
+# $norerr: never raise exceptions - return undef instead
+sub get_replicatable_volumes {
+    my ($class, $storecfg, $vmid, $conf, $cleanup, $noerr) = @_;
+
+    die "implement me - abstract method\n";
+}
+
 # Internal snapshots
 
 # NOTE: Snapshot create/delete involves several non-atomic
@@ -260,6 +427,15 @@ sub __snapshot_delete_vol_snapshot {
     die "abstract method - implement me\n";
 }
 
+# called during rollback prepare, and between config rollback and starting guest
+# can change config, e.g. for vmgenid
+# $data is shared across calls and passed to vm_start
+sub __snapshot_rollback_hook {
+    my ($class, $vmid, $conf, $snap, $prepare, $data) = @_;
+
+    return;
+}
+
 # Checks whether a volume snapshot is possible for this volume.
 sub __snapshot_rollback_vol_possible {
     my ($class, $volume, $snapname) = @_;
@@ -283,7 +459,7 @@ sub __snapshot_rollback_vm_stop {
 
 # Start the VM/CT after a rollback with restored vmstate.
 sub __snapshot_rollback_vm_start {
-    my ($class, $vmid, $vmstate, $forcemachine);
+    my ($class, $vmid, $vmstate, $data);
 
     die "abstract method - implement me\n";
 }
@@ -332,6 +508,7 @@ sub __snapshot_apply_config {
     # keep description and list of unused disks
     foreach my $k (keys %$conf) {
        next if !($k =~ m/^unused\d+$/ || $k eq 'description');
+
        $newconf->{$k} = $conf->{$k};
     }
 
@@ -467,9 +644,13 @@ sub snapshot_delete {
 
     my $prepare = 1;
 
-    my $snap;
     my $unused = [];
 
+    my $conf = $class->load_config($vmid);
+    my $snap = $conf->{snapshots}->{$snapname};
+
+    die "snapshot '$snapname' does not exist\n" if !defined($snap);
+
     $class->set_lock($vmid, 'snapshot-delete')
        if (!$drivehash); # doesn't already have a 'snapshot' lock
 
@@ -582,6 +763,17 @@ sub snapshot_rollback {
        return $res;
     };
 
+    my $repl_conf = PVE::ReplicationConfig->new();
+    if ($repl_conf->check_for_existing_jobs($vmid, 1)) {
+       # remove all replication snapshots
+       my $volumes = $class->get_replicatable_volumes($storecfg, $vmid, $conf, 1);
+       my $sorted_volids = [ sort keys %$volumes ];
+
+       # remove all local replication snapshots (jobid => undef)
+       my $logfunc = sub { my $line = shift; chomp $line; print "$line\n"; };
+       PVE::Replication::prepare($storecfg, $sorted_volids, undef, 0, undef, $logfunc);
+    }
+    
     my $snap = &$get_snapshot_config();
 
     $class->__snapshot_foreach_volume($snap, sub {
@@ -590,6 +782,8 @@ sub snapshot_rollback {
        $class->__snapshot_rollback_vol_possible($volume, $snapname);
     });
 
+    my $data = {};
+
     my $updatefn = sub {
 
        $conf = $class->load_config($vmid);
@@ -612,36 +806,24 @@ sub snapshot_rollback {
        } else {
            die "got wrong lock\n" if !($conf->{lock} && $conf->{lock} eq 'rollback');
            delete $conf->{lock};
-       }
-
-       # machine only relevant for Qemu
-       my $forcemachine;
 
-       if (!$prepare) {
            my $unused = $class->__snapshot_rollback_get_unused($conf, $snap);
 
            foreach my $volid (@$unused) {
                $class->add_unused_volume($conf, $volid);
            }
 
-           my $has_machine_config = defined($conf->{machine});
-
            # copy snapshot config to current config
            $conf = $class->__snapshot_apply_config($conf, $snap);
            $conf->{parent} = $snapname;
-
-           # Note: old code did not store 'machine', so we try to be smart
-           # and guess the snapshot was generated with kvm 1.4 (pc-i440fx-1.4).
-           $forcemachine = $conf->{machine} || 'pc-i440fx-1.4';
-           # we remove the 'machine' configuration if not explicitly specified
-           # in the original config.
-           delete $conf->{machine} if $snap->{vmstate} && !$has_machine_config;
        }
 
+       $class->__snapshot_rollback_hook($vmid, $conf, $snap, $prepare, $data);
+
        $class->write_config($vmid, $conf);
 
        if (!$prepare && $snap->{vmstate}) {
-           $class->__snapshot_rollback_vm_start($vmid, $snap->{vmstate}, $forcemachine);
+           $class->__snapshot_rollback_vm_start($vmid, $snap->{vmstate}, $data);
        }
     };
 
@@ -657,4 +839,18 @@ sub snapshot_rollback {
     $class->lock_config($vmid, $updatefn);
 }
 
+# bash completion helper
+
+sub snapshot_list {
+    my ($class, $vmid) = @_;
+
+    my $snapshot = eval {
+       my $conf = $class->load_config($vmid);
+       my $snapshots = $conf->{snapshots} || [];
+       [ sort keys %$snapshots ]
+    } || [];
+
+    return $snapshot;
+}
+
 1;