]> git.proxmox.com Git - pve-manager.git/blobdiff - PVE/CLI/pve6to7.pm
pve6to7: more fine-grained detection of misconfigured guest volumes
[pve-manager.git] / PVE / CLI / pve6to7.pm
index 4fe606f32ecc5cc93f33b378286a6e95e1831ab6..6dd7760cc7722b1901383f2dcf56b4be56be68e7 100644 (file)
@@ -15,13 +15,15 @@ use PVE::Cluster;
 use PVE::Corosync;
 use PVE::INotify;
 use PVE::JSONSchema;
+use PVE::NodeConfig;
 use PVE::RPCEnvironment;
 use PVE::Storage;
+use PVE::Storage::Plugin;
 use PVE::Tools qw(run_command split_list);
+use PVE::QemuConfig;
 use PVE::QemuServer;
 use PVE::VZDump::Common;
 
-use File::Slurp;
 use Term::ANSIColor;
 
 use PVE::CLIHandler;
@@ -606,12 +608,27 @@ sub check_cifs_credential_location {
 sub check_custom_pool_roles {
     log_info("Checking custom roles for pool permissions..");
 
-    my $raw = read_file('/etc/pve/user.cfg');
+    my $raw = eval { PVE::Tools::file_get_contents('/etc/pve/user.cfg'); };
+    if ($@) {
+       log_fail("Failed to read '/etc/pve/user.cfg' - $@");
+       return;
+    }
 
     my $roles = {};
-    while ($raw =~ /^\s*role:(.*?):(.*?):(.*?)\s*$/gm) {
-       my ($role, $privlist) = ($1, $2);
+    while ($raw =~ /^\s*(.+?)\s*$/gm) {
+       my $line = $1;
+       my @data;
+
+       foreach my $d (split (/:/, $line)) {
+           $d =~ s/^\s+//;
+           $d =~ s/\s+$//;
+           push @data, $d
+       }
 
+       my $et = shift @data;
+       next if $et ne 'role';
+
+       my ($role, $privlist) = @data;
        if (!PVE::AccessControl::verify_rolename($role, 1)) {
            warn "user config - ignore role '$role' - invalid characters in role name\n";
            next;
@@ -646,6 +663,227 @@ sub check_custom_pool_roles {
     }
 }
 
+my sub check_max_length {
+    my ($raw, $max_length, $warning) = @_;
+    log_warn($warning) if defined($raw) && length($raw) > $max_length; 
+}
+
+sub check_description_lengths {
+    log_info("Checking node and guest description/note legnth..");
+
+    my @affected_nodes = grep {
+       my $desc = PVE::NodeConfig::load_config($_)->{desc};
+       defined($desc) && length($desc) > 64 * 1024
+    } PVE::Cluster::get_nodelist();
+
+    if (scalar(@affected_nodes) > 0) {
+       log_warn("Node config description of the following nodes too long for new limit of 64 KiB:\n    "
+           . join(', ', @affected_nodes));
+    } else {
+       log_pass("All node config descriptions fit in the new limit of 64 KiB");
+    }
+
+    my $affected_guests = [];
+
+    my $cts = PVE::LXC::config_list();
+    for my $vmid (sort { $a <=> $b } keys %$cts) {
+       my $desc = PVE::LXC::Config->load_config($vmid)->{description};
+       push @$affected_guests, "CT $vmid" if defined($desc) && length($desc) > 8 * 1024;
+    }
+    my $vms = PVE::QemuServer::config_list();
+    for my $vmid (sort { $a <=> $b } keys %$vms) {
+       my $desc = PVE::QemuConfig->load_config($vmid)->{description};
+       push @$affected_guests, "VM $vmid" if defined($desc) && length($desc) > 8 * 1024;
+    }
+    if (scalar($affected_guests->@*) > 0) {
+       log_warn("Node config description of the following nodes too long for new limit of 64 KiB:\n"
+           ."    * " . join("\n    * ", $affected_guests->@*));
+    } else {
+       log_pass("All guest config descriptions fit in the new limit of 8 KiB");
+    }
+}
+
+sub check_storage_content {
+    log_info("Checking storage content type configuration..");
+
+    my $found_referenced;
+    my $found_unreferenced;
+    my $pass = 1;
+
+    my $storage_cfg = PVE::Storage::config();
+
+    my $potentially_affected = {};
+    my $referenced_volids = {};
+
+    for my $storeid (keys $storage_cfg->{ids}->%*) {
+       my $scfg = $storage_cfg->{ids}->{$storeid};
+
+       next if !PVE::Storage::storage_check_enabled($storage_cfg, $storeid, undef, 1);
+
+       my $valid_content = PVE::Storage::Plugin::valid_content_types($scfg->{type});
+
+       if (scalar(keys $scfg->{content}->%*) == 0 && !$valid_content->{none}) {
+           $pass = 0;
+           log_fail("storage '$storeid' does not support configured content type 'none'");
+           delete $scfg->{content}->{none}; # scan for guest images below
+       }
+
+       next if $scfg->{content}->{images} && $scfg->{content}->{rootdir};
+
+       # Skip 'iscsi(direct)' (and foreign plugins with potentially similiar behavior) with 'none',
+       # because that means "use LUNs directly" and vdisk_list() in PVE 6.x still lists those.
+       # It's enough to *not* skip 'dir', because it is the only other storage that supports 'none'
+       # and 'images' or 'rootdir', hence being potentially misconfigured.
+       next if $scfg->{type} ne 'dir' && $scfg->{content}->{none};
+
+       eval { PVE::Storage::activate_storage($storage_cfg, $storeid) };
+       if (my $err = $@) {
+           log_warn("activating '$storeid' failed - $err");
+           next;
+       }
+
+       my $res = eval { PVE::Storage::vdisk_list($storage_cfg, $storeid); };
+       if (my $err = $@) {
+           log_warn("listing images on '$storeid' failed - $err");
+           next;
+       }
+       my @volids = map { $_->{volid} } $res->{$storeid}->@*;
+
+       for my $volid (@volids) {
+           $potentially_affected->{$volid} = 1;
+       }
+
+       my $number = scalar(@volids);
+       if ($number > 0 && !$scfg->{content}->{images} && !$scfg->{content}->{rootdir}) {
+           log_info("storage '$storeid' - neither content type 'images' nor 'rootdir' configured"
+               .", but found $number guest volume(s)");
+       }
+    }
+
+    my $check_volid = sub {
+       my ($volid, $vmid, $vmtype, $reference) = @_;
+
+       $referenced_volids->{$volid} = 1 if $reference ne 'unreferenced';
+
+       my $guesttext = $vmtype eq 'qemu' ? 'VM' : 'CT';
+       my $prefix = "$guesttext $vmid - volume '$volid' ($reference)";
+
+       my ($storeid) = PVE::Storage::parse_volume_id($volid, 1);
+       return if !defined($storeid);
+
+       my $scfg = $storage_cfg->{ids}->{$storeid};
+       if (!$scfg) {
+           $pass = 0;
+           log_warn("$prefix - storage does not exist!");
+           return;
+       }
+
+       # cannot use parse_volname for containers, as it can return 'images'
+       # but containers cannot have ISO images attached, so assume 'rootdir'
+       my $vtype = 'rootdir';
+       if ($vmtype eq 'qemu') {
+           ($vtype) = eval { PVE::Storage::parse_volname($storage_cfg, $volid); };
+           return if $@;
+       }
+
+       if (!$scfg->{content}->{$vtype}) {
+           $found_referenced = 1 if $reference ne 'unreferenced';
+           $found_unreferenced = 1 if $reference eq 'unreferenced';
+           $pass = 0;
+           log_warn("$prefix - storage does not have content type '$vtype' configured.");
+       }
+    };
+
+    my $guests = {};
+
+    my $cts = PVE::LXC::config_list();
+    for my $vmid (sort { $a <=> $b } keys %$cts) {
+       $guests->{$vmid} = 'lxc';
+
+       my $conf = PVE::LXC::Config->load_config($vmid);
+
+       my $volhash = {};
+
+       my $check = sub {
+           my ($ms, $mountpoint, $reference) = @_;
+
+           my $volid = $mountpoint->{volume};
+           return if !$volid || $mountpoint->{type} ne 'volume';
+
+           return if $volhash->{$volid}; # volume might be referenced multiple times
+
+           $volhash->{$volid} = 1;
+
+           $check_volid->($volid, $vmid, 'lxc', $reference);
+       };
+
+       my $opts = { include_unused => 1 };
+       PVE::LXC::Config->foreach_volume_full($conf, $opts, $check, 'in config');
+       for my $snapname (keys $conf->{snapshots}->%*) {
+           my $snap = $conf->{snapshots}->{$snapname};
+           PVE::LXC::Config->foreach_volume_full($snap, $opts, $check, "in snapshot '$snapname'");
+       }
+    }
+
+    my $vms = PVE::QemuServer::config_list();
+    for my $vmid (sort { $a <=> $b } keys %$vms) {
+       $guests->{$vmid} = 'qemu';
+
+       my $conf = PVE::QemuConfig->load_config($vmid);
+
+       my $volhash = {};
+
+       my $check = sub {
+           my ($key, $drive, $reference) = @_;
+
+           my $volid = $drive->{file};
+           return if $volid =~ m|^/|;
+
+           return if $volhash->{$volid}; # volume might be referenced multiple times
+
+           $volhash->{$volid} = 1;
+
+           $check_volid->($volid, $vmid, 'qemu', $reference);
+       };
+
+       my $opts = {
+           extra_keys => ['vmstate'],
+           include_unused => 1,
+       };
+       # startup from a suspended state works even without 'images' content type on the
+       # state storage, so do not check 'vmstate' for $conf
+       PVE::QemuConfig->foreach_volume_full($conf, { include_unused => 1 }, $check, 'in config');
+       for my $snapname (keys $conf->{snapshots}->%*) {
+           my $snap = $conf->{snapshots}->{$snapname};
+           PVE::QemuConfig->foreach_volume_full($snap, $opts, $check, "in snapshot '$snapname'");
+       }
+    }
+
+    if ($found_referenced) {
+       log_warn("Proxmox VE 7.0 enforces stricter content type checks. The guests above " .
+           "might not work until the storage configuration is fixed.");
+    }
+
+    for my $volid (sort keys $potentially_affected->%*) {
+       next if $referenced_volids->{$volid}; # already checked
+
+       my (undef, undef, $vmid) = PVE::Storage::parse_volname($storage_cfg, $volid);
+       my $vmtype = $guests->{$vmid};
+       next if !$vmtype;
+
+       $check_volid->($volid, $vmid, $vmtype, 'unreferenced');
+    }
+
+    if ($found_unreferenced) {
+       log_warn("When migrating, Proxmox VE 7.0 only scans storages with the appropriate " .
+           "content types for unreferenced guest volumes.");
+    }
+
+    if ($pass) {
+       log_pass("no problems found");
+    }
+}
+
 sub check_misc {
     print_header("MISCELLANEOUS CHECKS");
     my $ssh_config = eval { PVE::Tools::file_get_contents('/root/.ssh/config') };
@@ -662,8 +900,8 @@ sub check_misc {
     $log_systemd_unit_state->('pvestatd.service');
 
     my $root_free = PVE::Tools::df('/', 10);
-    log_warn("Less than 2G free space on root file system.")
-       if defined($root_free) && $root_free->{avail} < 2*1024*1024*1024;
+    log_warn("Less than 4 GiB free space on root file system.")
+       if defined($root_free) && $root_free->{avail} < 4*1024*1024*1024;
 
     log_info("Checking for running guests..");
     my $running_guests = 0;
@@ -739,6 +977,8 @@ sub check_misc {
     check_backup_retention_settings();
     check_cifs_credential_location();
     check_custom_pool_roles();
+    check_description_lengths();
+    check_storage_content();
 }
 
 __PACKAGE__->register_method ({