]> git.proxmox.com Git - pve-container.git/blobdiff - src/PVE/LXC.pm
Refactor has_feature
[pve-container.git] / src / PVE / LXC.pm
index 804793e1bb33abc5365a6fc50f737f781e53719d..503a57fb872ada093e83b101349afe851950baf3 100644 (file)
@@ -12,6 +12,7 @@ use Cwd qw();
 use Fcntl qw(O_RDONLY);
 
 use PVE::Cluster qw(cfs_register_file cfs_read_file);
+use PVE::Exception qw(raise_perm_exc);
 use PVE::Storage;
 use PVE::SafeSyslog;
 use PVE::INotify;
@@ -119,8 +120,8 @@ my $confdesc = {
     ostype => {
        optional => 1,
        type => 'string',
-       enum => ['debian', 'ubuntu', 'centos', 'fedora', 'opensuse', 'archlinux', 'alpine'],
-       description => "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
+       enum => ['debian', 'ubuntu', 'centos', 'fedora', 'opensuse', 'archlinux', 'alpine', 'unmanaged'],
+       description => "OS type. This is used to setup configuration inside the container, and corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf. Value 'unmanaged' can be used to skip and OS specific setup.",
     },
     console => {
        optional => 1,
@@ -1082,7 +1083,7 @@ sub update_lxc_config {
     my $custom_idmap = grep { $_->[0] eq 'lxc.id_map' } @{$conf->{lxc}};
 
     my $ostype = $conf->{ostype} || die "missing 'ostype' - internal error";
-    if ($ostype =~ /^(?:debian | ubuntu | centos | fedora | opensuse | archlinux | alpine)$/x) {
+    if ($ostype =~ /^(?:debian | ubuntu | centos | fedora | opensuse | archlinux | alpine | unmanaged)$/x) {
        my $inc ="/usr/share/lxc/config/$ostype.common.conf";
        $inc ="/usr/share/lxc/config/common.conf" if !-f $inc;
        $raw .= "lxc.include = $inc\n";
@@ -1198,7 +1199,7 @@ sub verify_searchdomain_list {
 }
 
 sub is_volume_in_use {
-    my ($config, $volid) = @_;
+    my ($config, $volid, $include_snapshots) = @_;
     my $used = 0;
 
     foreach_mountpoint($config, sub {
@@ -1209,6 +1210,13 @@ sub is_volume_in_use {
        }
     });
 
+    my $snapshots = $config->{snapshots};
+    if ($include_snapshots && $snapshots) {
+       foreach my $snap (keys %$snapshots) {
+           $used ||= is_volume_in_use($snapshots->{$snap}, $volid);
+       }
+    }
+
     return $used;
 }
 
@@ -1399,6 +1407,9 @@ sub update_pct_config {
            $used_volids->{$mp->{volume}} = 1;
        } elsif ($opt eq 'unprivileged') {
            die "unable to modify read-only option: '$opt'\n";
+       } elsif ($opt eq 'ostype') {
+           next if $hotplug_error->($opt);
+           $conf->{$opt} = $value;
        } else {
            die "implement me: $opt";
        }
@@ -1421,6 +1432,8 @@ sub update_pct_config {
        my $storage_cfg = PVE::Storage::config();
        foreach my $volume (@deleted_volumes) {
            next if $used_volids->{$volume}; # could have been re-added, too
+           # also check for references in snapshots
+           next if is_volume_in_use($conf, $volume, 1);
            delete_mountpoint_volume($storage_cfg, $vmid, $volume);
        }
     }
@@ -1787,9 +1800,9 @@ my $snapshot_apply_config = sub {
     return $newconf;
 };
 
-my $snapshot_save_vmstate = sub {
+sub snapshot_save_vmstate {
     die "implement me - snapshot_save_vmstate\n";
-};
+}
 
 sub snapshot_prepare {
     my ($vmid, $snapname, $save_vmstate, $comment) = @_;
@@ -1811,15 +1824,13 @@ sub snapshot_prepare {
            if defined($conf->{snapshots}->{$snapname});
 
        my $storecfg = PVE::Storage::config();
-
-       # workaround until mp snapshots are implemented
-       my $feature = $snapname eq 'vzdump' ? 'vzdump' : 'snapshot';
-       die "snapshot feature is not available\n" if !has_feature($feature, $conf, $storecfg);
+       die "snapshot feature is not available\n"
+           if !has_feature('snapshot', $conf, $storecfg, undef, undef, $snapname eq 'vzdump');
 
        $snap = $conf->{snapshots}->{$snapname} = {};
 
        if ($save_vmstate && check_running($vmid)) {
-           &$snapshot_save_vmstate($vmid, $conf, $snapname, $storecfg);
+           snapshot_save_vmstate($vmid, $conf, $snapname, $storecfg);
        }
 
        &$snapshot_copy_config($conf, $snap);
@@ -1866,23 +1877,17 @@ sub snapshot_commit {
 }
 
 sub has_feature {
-    my ($feature, $conf, $storecfg, $snapname) = @_;
+    my ($feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
     
     my $err;
-    my $vzdump = $feature eq 'vzdump';
-    $feature = 'snapshot' if $vzdump;
 
     foreach_mountpoint($conf, sub {
        my ($ms, $mountpoint) = @_;
 
        return if $err; # skip further test
-       return if $vzdump && $ms ne 'rootfs' && !$mountpoint->{backup};
+       return if $backup_only && $ms ne 'rootfs' && !$mountpoint->{backup};
        
-       $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $mountpoint->{volume}, $snapname);
-
-       # TODO: implement support for mountpoints
-       die "unable to handle mountpoint '$ms' - feature not implemented\n"
-           if $ms ne 'rootfs';
+       $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $mountpoint->{volume}, $snapname, $running);
     });
 
     return $err ? 0 : 1;
@@ -1957,43 +1962,54 @@ sub sync_container_namespace {
     die "failed to sync container namespace\n" if $? != 0;
 }
 
+sub check_freeze_needed {
+    my ($vmid, $config, $save_vmstate) = @_;
+
+    my $ret = check_running($vmid);
+    return ($ret, $ret);
+}
+
 sub snapshot_create {
     my ($vmid, $snapname, $save_vmstate, $comment) = @_;
 
     my $snap = snapshot_prepare($vmid, $snapname, $save_vmstate, $comment);
 
+    $save_vmstate = 0 if !$snap->{vmstate};
+
     my $conf = load_config($vmid);
 
-    my $running = check_running($vmid);
-    
-    my $unfreeze = 0;
+    my ($running, $freezefs) = check_freeze_needed($vmid, $conf, $snap->{vmstate});
 
     my $drivehash = {};
 
     eval {
-       if ($running) {
-           $unfreeze = 1;
+       if ($freezefs) {
            PVE::Tools::run_command(['/usr/bin/lxc-freeze', '-n', $vmid]);
            sync_container_namespace($vmid);
-       };
+       }
 
        my $storecfg = PVE::Storage::config();
-       my $rootinfo = parse_ct_rootfs($conf->{rootfs});
-       my $volid = $rootinfo->{volume};
+       foreach_mountpoint($conf, sub {
+           my ($ms, $mountpoint) = @_;
 
-       PVE::Storage::volume_snapshot($storecfg, $volid, $snapname);
-       $drivehash->{rootfs} = 1;
+           return if $snapname eq 'vzdump' && $ms ne 'rootfs' && !$mountpoint->{backup};
+           PVE::Storage::volume_snapshot($storecfg, $mountpoint->{volume}, $snapname);
+           $drivehash->{$ms} = 1;
+       });
     };
     my $err = $@;
     
-    if ($unfreeze) {
-       eval { PVE::Tools::run_command(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
-       warn $@ if $@;
+    if ($running) {
+       if ($freezefs) {
+           eval { PVE::Tools::run_command(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
+           warn $@ if $@;
+       }
     }
     
     if ($err) {
+       warn "snapshot create failed: starting cleanup\n";
        eval { snapshot_delete($vmid, $snapname, 1, $drivehash); };
-       warn "$@\n" if $@;
+       warn "$@" if $@;
        die "$err\n";
     }
 
@@ -2007,6 +2023,7 @@ sub snapshot_delete {
     my $prepare = 1;
 
     my $snap;
+    my $unused = [];
 
     my $unlink_parent = sub {
        my ($confref, $new_parent) = @_;
@@ -2048,7 +2065,11 @@ sub snapshot_delete {
            if ($remove_drive eq 'vmstate') {
                die "implement me - saving vmstate\n";
            } else {
-               die "implement me - remove drive\n";
+               my $value = $snap->{$remove_drive};
+               my $mountpoint = $remove_drive eq 'rootfs' ? parse_ct_rootfs($value, 1) : parse_ct_mountpoint($value, 1);
+               delete $snap->{$remove_drive};
+               add_unused_volume($snap, $mountpoint->{volume})
+                   if (!is_volume_in_use($snap, $mountpoint->{volume}));
            }
        }
 
@@ -2057,6 +2078,10 @@ sub snapshot_delete {
        } else {
            delete $conf->{snapshots}->{$snapname};
            delete $conf->{lock} if $drivehash;
+           foreach my $volid (@$unused) {
+               add_unused_volume($conf, $volid)
+                   if (!is_volume_in_use($conf, $volid));
+           }
        }
 
        write_config($vmid, $conf);
@@ -2073,17 +2098,22 @@ sub snapshot_delete {
     };
 
     # now remove all volume snapshots
-    # only rootfs for now!
-    eval {
-       my $rootfs = $snap->{rootfs};
-       my $rootinfo = parse_ct_rootfs($rootfs);
-       my $volid = $rootinfo->{volume};
-       PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snapname);
-    };
-    if (my $err = $@) {
-       die $err if !$force;
-       warn $err;
-    }
+    foreach_mountpoint($snap, sub {
+       my ($ms, $mountpoint) = @_;
+
+       return if $snapname eq 'vzdump' && $ms ne 'rootfs' && !$mountpoint->{backup};
+       if (!$drivehash || $drivehash->{$ms}) {
+           eval { PVE::Storage::volume_snapshot_delete($storecfg, $mountpoint->{volume}, $snapname); };
+           if (my $err = $@) {
+               die $err if !$force;
+               warn $err;
+           }
+       }
+
+       # save changes (remove mp from snapshot)
+       lock_config($vmid, $updatefn, $ms) if !$force;
+       push @$unused, $mountpoint->{volume};
+    });
 
     # now cleanup config
     $prepare = 0;
@@ -2112,12 +2142,11 @@ sub snapshot_rollback {
 
     my $snap = &$get_snapshot_config();
 
-    # only for rootfs for now!
-    my $rootfs = $snap->{rootfs};
-    my $rootinfo = parse_ct_rootfs($rootfs);
-    my $volid = $rootinfo->{volume};
+    foreach_mountpoint($snap, sub {
+       my ($ms, $mountpoint) = @_;
 
-    PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname);
+       PVE::Storage::volume_rollback_is_possible($storecfg, $mountpoint->{volume}, $snapname);
+    });
 
     my $updatefn = sub {
 
@@ -2130,7 +2159,8 @@ sub snapshot_rollback {
 
        if ($prepare) {
            check_lock($conf);
-           system("lxc-stop -n $vmid --kill") if check_running($vmid);
+           PVE::Tools::run_command(['/usr/bin/lxc-stop', '-n', $vmid, '--kill'])
+               if check_running($vmid);
        }
 
        die "unable to rollback vm $vmid: vm is running\n"
@@ -2154,14 +2184,17 @@ sub snapshot_rollback {
        write_config($vmid, $conf);
 
        if (!$prepare && $snap->{vmstate}) {
-           die "implement me - save vmstate";
+           die "implement me - save vmstate\n";
        }
     };
 
     lock_config($vmid, $updatefn);
 
-    # only rootfs for now!
-    PVE::Storage::volume_snapshot_rollback($storecfg, $volid, $snapname);
+    foreach_mountpoint($snap, sub {
+       my ($ms, $mountpoint) = @_;
+
+       PVE::Storage::volume_snapshot_rollback($storecfg, $mountpoint->{volume}, $snapname);
+    });
 
     $prepare = 0;
     lock_config($vmid, $updatefn);
@@ -2232,16 +2265,20 @@ sub foreach_mountpoint_reverse {
 }
 
 sub check_ct_modify_config_perm {
-    my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
-
-    return 1 if $authuser ne 'root@pam';
+    my ($rpcenv, $authuser, $vmid, $pool, $newconf, $delete) = @_;
 
-    foreach my $opt (@$key_list) {
+    return 1 if $authuser eq 'root@pam';
 
+    my $check = sub {
+       my ($opt, $delete) = @_;
        if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
            $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
        } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
            $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
+           return if $delete;
+           my $data = $opt eq 'rootfs' ? parse_ct_rootfs($newconf->{$opt})
+                                       : parse_ct_mountpoint($newconf->{$opt});
+           raise_perm_exc("mountpoint type $data->{type}") if $data->{type} ne 'volume';
        } elsif ($opt eq 'memory' || $opt eq 'swap') {
            $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
        } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
@@ -2250,6 +2287,13 @@ sub check_ct_modify_config_perm {
        } else {
            $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
        }
+    };
+
+    foreach my $opt (keys %$newconf) {
+       &$check($opt, 0);
+    }
+    foreach my $opt (@$delete) {
+       &$check($opt, 1);
     }
 
     return 1;