]>
git.proxmox.com Git - pve-manager.git/blob - PVE/CLI/pve5to6.pm
1 package PVE
::CLI
::pve5to6
;
16 use PVE
::RPCEnvironment
;
25 use base
qw(PVE::CLIHandler);
27 my $nodename = PVE
::INotify
::nodename
();
29 sub setup_environment
{
30 PVE
::RPCEnvironment-
>setup_default_cli_env();
33 my $min_pve_major = 5;
34 my $min_pve_minor = 4;
35 my $min_pve_pkgrel = 2;
45 my ($level, $line) = @_;
47 $counters->{$level}++ if defined($level) && defined($counters->{$level});
49 print uc($level), ': ' if defined($level);
55 $log_line->('pass', @_);
60 $log_line->('info', @_);
63 $log_line->('skip', @_);
66 print color
('yellow');
67 $log_line->('warn', @_);
72 $log_line->('fail', @_);
76 my $print_header_first = 1;
79 print "\n" if !$print_header_first;
81 $print_header_first = 0;
88 $versions = eval { PVE
::API2
::APT-
>versions({ node
=> $nodename }) } if !defined($versions);
90 if (!defined($versions)) {
91 my $msg = "unable to retrieve package version information";
97 my $pkgs = [ grep { $_->{Package
} eq $pkg } @$versions ];
98 if (!defined $pkgs || $pkgs == 0) {
99 log_fail
("unable to determine installed $pkg version.");
106 sub check_pve_packages
{
107 print_header
("CHECKING VERSION INFORMATION FOR PVE PACKAGES");
109 print "Checking for package updates..\n";
110 my $updates = eval { PVE
::API2
::APT-
>list_updates({ node
=> $nodename }); };
111 if (!defined($updates)) {
112 log_warn
("$@") if $@;
113 log_fail
("unable to retrieve list of package updates!");
114 } elsif (@$updates > 0) {
115 my $pkgs = join(', ', map { $_->{Package
} } @$updates);
116 log_warn
("updates for the following packages are available:\n $pkgs");
118 log_pass
("all packages uptodate");
121 print "\nChecking proxmox-ve package version..\n";
122 if (defined(my $proxmox_ve = $get_pkg->('proxmox-ve'))) {
123 my $min_pve_ver = "$min_pve_major.$min_pve_minor-$min_pve_pkgrel";
125 my ($maj, $min, $pkgrel) = $proxmox_ve->{OldVersion
} =~ m/^(\d+)\.(\d+)-(\d+)/;
129 if ($maj > $min_pve_major) {
130 log_pass
("already upgraded to Proxmox VE " . ($min_pve_major + 1));
132 } elsif ($maj >= $min_pve_major && $min >= $min_pve_minor && $pkgrel >= $min_pve_pkgrel) {
133 log_pass
("proxmox-ve package has version >= $min_pve_ver");
135 log_fail
("proxmox-ve package is too old, please upgrade to >= $min_pve_ver!");
138 my ($krunning, $kinstalled) = (qr/5\./, 'pve-kernel-5.0');
140 ($krunning, $kinstalled) = (qr/4\.15/, 'pve-kernel-4.15');
143 print "\nChecking running kernel version..\n";
144 my $kernel_ver = $proxmox_ve->{RunningKernel
};
145 if (!defined($kernel_ver)) {
146 log_fail
("unable to determine running kernel version.");
147 } elsif ($kernel_ver =~ /^$krunning/) {
148 log_pass
("expected running kernel '$kernel_ver'.");
149 } elsif ($get_pkg->($kinstalled)) {
150 log_warn
("expected kernel '$kinstalled' intalled but not yet rebooted!");
152 log_warn
("unexpected running and installed kernel '$kernel_ver'.");
157 sub get_vms_with_vmx
{
162 my $vmlist = PVE
::QemuServer
::vzlist
();
164 foreach my $vmid ( sort { $a <=> $b } keys %$vmlist ) {
165 my $pid = $vmlist->{$vmid}->{pid
};
166 next if !$pid; # skip not running vms
168 my $cmdline = eval { PVE
::Tools
::file_get_contents
("/proc/$pid/cmdline") };
170 my @args = split(/\0/, $cmdline);
171 for (my $i = 0; $i < scalar(@args); $i++) {
172 next if !$args[$i] || $args[$i] !~ m/^-?-cpu$/;
174 my $cpuarg = $args[$i+1];
175 if ($cpuarg =~ m/^(host|max)/) {
176 push @{$res->{cpu
}}, $vmid;
177 } elsif ($cpuarg =~ m/\+(vmx|svm)/) {
178 push @{$res->{flag
}}, $vmid;
184 $res = undef if (scalar(@{$res->{cpu
}}) + scalar(@{$res->{flag
}})) <= 0;
189 sub check_kvm_nested
{
190 log_info
("Checking KVM nesting support, which breaks live migration for VMs using it..");
192 my $module_sysdir = "/sys/module";
193 if (-e
"$module_sysdir/kvm_amd") {
194 $module_sysdir .= "/kvm_amd/parameters";
195 } elsif (-e
"$module_sysdir/kvm_intel") {
196 $module_sysdir .= "/kvm_intel/parameters";
198 log_skip
("no kvm module found");
202 if (-f
"$module_sysdir/nested") {
203 my $val = eval { PVE
::Tools
::file_read_firstline
("$module_sysdir/nested") };
204 if ($val && $val =~ m/Y|1/) {
205 my $list = get_vms_with_vmx
();
206 if (!defined($list)) {
207 log_pass
("KVM nested parameter set, but currently no VM with a 'vmx' or 'svm' flag is running.");
209 my $warnmsg = "KVM nested enabled. It will not be possible to live migrate the following running VMs to PVE 6:\n";
210 if (@{$list->{cpu
}}) {
211 $warnmsg .= " VMID(s) with cputype 'host' or 'max': " . join(',', @{$list->{cpu
}}) . "\n";
213 if (@{$list->{flag
}}) {
214 $warnmsg .= " VMID(s) with enforced cpu flag 'vmx' or 'svm': " . join(',', @{$list->{flag
}}) . "\n";
219 log_pass
("KVM nested parameter not set.")
222 log_skip
("KVM nested parameter not found.");
226 sub check_storage_health
{
227 print_header
("CHECKING CONFIGURED STORAGES");
228 my $cfg = PVE
::Storage
::config
();
232 my $info = PVE
::Storage
::storage_info
($cfg);
234 foreach my $storeid (keys %$info) {
235 my $d = $info->{$storeid};
237 if ($d->{type
} eq 'sheepdog') {
238 log_fail
("storage '$storeid' of type 'sheepdog' is enabled - experimental sheepdog support dropped in PVE 6")
239 } elsif ($d->{active
}) {
240 log_pass
("storage '$storeid' enabled and active.");
242 log_warn
("storage '$storeid' enabled but not active!");
245 log_skip
("storage '$storeid' disabled.");
250 sub check_cluster_corosync
{
251 print_header
("CHECKING CLUSTER HEALTH/SETTINGS");
253 if (!PVE
::Corosync
::check_conf_exists
(1)) {
254 log_skip
("standalone node.");
258 if (PVE
::Cluster
::check_cfs_quorum
(1)) {
259 log_pass
("Cluster is quorate.");
261 log_fail
("Cluster lost quorum!");
264 my $conf = PVE
::Cluster
::cfs_read_file
('corosync.conf');
265 my $conf_nodelist = PVE
::Corosync
::nodelist
($conf);
267 if (!defined($conf_nodelist)) {
268 log_fail
("unable to retrieve nodelist from corosync.conf");
269 } elsif (grep { $conf_nodelist->{$_}->{quorum_votes
} != 1 } keys %$conf_nodelist) {
270 log_warn
("non-default quorum_votes distribution detected!");
273 my $cfs_nodelist = PVE
::Cluster
::get_clinfo
()->{nodelist
};
274 my $offline_nodes = grep { $cfs_nodelist->{$_}->{online
} != 1 } keys %$cfs_nodelist;
275 if ($offline_nodes > 0) {
276 log_fail
("$offline_nodes nodes are offline!");
279 my $conf_nodelist_count = scalar(keys %$conf_nodelist);
280 my $cfs_nodelist_count = scalar(keys %$cfs_nodelist);
281 log_warn
("cluster consists of less than three nodes!")
282 if $conf_nodelist_count < 3;
284 log_fail
("corosync.conf ($conf_nodelist_count) and pmxcfs ($cfs_nodelist_count) don't agree about size of nodelist.")
285 if $conf_nodelist_count != $cfs_nodelist_count;
287 foreach my $cs_node (keys %$conf_nodelist) {
288 my $entry = $conf_nodelist->{$cs_node};
289 log_fail
("No name entry for node '$cs_node' in corosync.conf.")
290 if !defined($entry->{name
});
291 log_fail
("No nodeid configured for node '$cs_node' in corosync.conf.")
292 if !defined($entry->{nodeid
});
294 my $verify_ring_ip = sub {
296 my $ring = $entry->{$key};
297 if (defined($ring) && !PVE
::JSONSchema
::pve_verify_ip
($ring, 1)) {
298 log_fail
("$key '$ring' of node '$cs_node' is not an IP address, consider replacing it with the currently resolved IP address.");
301 $verify_ring_ip->('ring0_addr');
302 $verify_ring_ip->('ring1_addr');
305 my $totem = $conf->{main
}->{totem
};
307 my $transport = $totem->{transport
};
308 if (defined($transport)) {
309 log_fail
("Corosync transport expliclitly set to '$transport' instead of implicit default!");
312 if ((!defined($totem->{secauth
}) || $totem->{secauth
} ne 'on') && (!defined($totem->{crypto_cipher
}) || $totem->{crypto_cipher
} eq 'none')) {
313 log_fail
("Corosync authentication/encryption is not explicitly enabled (secauth / crypto_cipher / crypto_hash)!");
316 if (defined($totem->{crypto_cipher
}) && $totem->{crypto_cipher
} eq '3des') {
317 log_fail
("Corosync encryption cipher set to '3des', no longer supported in Corosync 3.x!");
320 my $prefix_info = sub { my $line = shift; log_info
("$line"); };
323 log_info
("Printing detailed cluster status..");
324 PVE
::Tools
::run_command
(['corosync-quorumtool', '-siH'], outfunc
=> $prefix_info, errfunc
=> $prefix_info);
327 print_header
("CHECKING INSTALLED COROSYNC VERSION");
328 if (defined(my $corosync = $get_pkg->('corosync'))) {
329 if ($corosync->{OldVersion
} =~ m/^2\./) {
330 log_fail
("corosync 2.x installed, cluster-wide upgrade to 3.x needed!");
331 } elsif ($corosync->{OldVersion
} =~ m/^3\./) {
332 log_pass
("corosync 3.x installed.");
334 log_fail
("unexpected corosync version installed: $corosync->{OldVersion}!");
340 print_header
("CHECKING HYPER-CONVERGED CEPH STATUS");
342 if (PVE
::Ceph
::Tools
::check_ceph_inited
(1)) {
343 log_info
("hyper-converged ceph setup detected!");
345 log_skip
("no hyper-converged ceph setup detected!");
349 log_info
("getting Ceph status/health information..");
350 my $ceph_status = eval { PVE
::API2
::Ceph-
>status({ node
=> $nodename }); };
351 my $osd_flags = eval { PVE
::API2
::Ceph-
>get_flags({ node
=> $nodename }); };
353 $noout = $osd_flags =~ m/noout/ if $osd_flags;
355 if (!$ceph_status || !$ceph_status->{health
}) {
356 log_fail
("unable to determine Ceph status!");
358 my $ceph_health = $ceph_status->{health
}->{status
};
360 log_fail
("unable to determine Ceph health!");
361 } elsif ($ceph_health eq 'HEALTH_OK') {
362 log_pass
("Ceph health reported as 'HEALTH_OK'.");
363 } elsif ($ceph_health eq 'HEALTH_WARN' && $noout && (keys %{$ceph_status->{health
}->{checks
}} == 1)) {
364 log_pass
("Ceph health reported as 'HEALTH_WARN' with a single failing check and 'noout' flag set.");
366 log_warn
("Ceph health reported as '$ceph_health'.\n Use the PVE ".
367 "dashboard or 'ceph -s' to determine the specific issues and try to resolve them.");
371 log_info
("getting Ceph OSD flags..");
374 log_fail
("unable to get Ceph OSD flags!");
376 if ($osd_flags =~ m/recovery_deletes/ && $osd_flags =~ m/purged_snapdirs/) {
377 log_pass
("all PGs have been scrubbed at least once while running Ceph Luminous.");
379 log_fail
("missing 'recovery_deletes' and/or 'purged_snapdirs' flag, scrub of all PGs required before upgrading to Nautilus!");
382 log_pass
("noout flag set to prevent rebalancing during cluster-wide upgrades.");
384 log_warn
("noout flag not set - recommended to prevent rebalancing during upgrades.");
389 log_info
("getting Ceph daemon versions..");
390 my $ceph_versions = eval { PVE
::Ceph
::Tools
::get_cluster_versions
(undef, 1); };
391 if (!$ceph_versions) {
392 log_fail
("unable to determine Ceph daemon versions!");
395 { 'key' => 'mon', 'name' => 'monitor' },
396 { 'key' => 'mgr', 'name' => 'manager' },
397 { 'key' => 'mds', 'name' => 'MDS' },
398 { 'key' => 'osd', 'name' => 'OSD' },
401 foreach my $service (@$services) {
402 my $name = $service->{name
};
403 if (my $service_versions = $ceph_versions->{$service->{key
}}) {
404 if (keys %$service_versions == 0) {
405 log_skip
("no running instances detected for daemon type $name.");
406 } elsif (keys %$service_versions == 1) {
407 log_pass
("single running version detected for daemon type $name.");
409 log_warn
("multiple running versions detected for daemon type $name!");
412 log_skip
("unable to determine versions of running Ceph $name instances.");
416 my $overall_versions = $ceph_versions->{overall
};
417 if (!$overall_versions) {
418 log_warn
("unable to determine overall Ceph daemon versions!");
419 } elsif (keys %$overall_versions == 1) {
420 log_pass
("single running overall version detected for all Ceph daemon types.");
422 log_warn
("overall version mismatch detected, check 'ceph versions' output for details!");
426 my $local_ceph_ver = PVE
::Ceph
::Tools
::get_local_version
(1);
427 if (defined($local_ceph_ver)) {
428 if ($local_ceph_ver == 14) {
429 my $scanned_osds = PVE
::Tools
::dir_glob_regex
('/etc/ceph/osd', '^.*\.json$');
430 if (-e
'/var/lib/ceph/osd/' && !defined($scanned_osds)) {
431 log_warn
("local Ceph version is Nautilus, local OSDs detected, but no conversion from ceph-disk to ceph-volume done (yet).");
435 log_fail
("unable to determine local Ceph version.");
440 print_header
("MISCELLANEOUS CHECKS");
441 my $ssh_config = eval { PVE
::Tools
::file_get_contents
('/root/.ssh/config') };
442 if (defined($ssh_config)) {
443 log_fail
("Unsupported SSH Cipher configured for root in /root/.ssh/config: $1")
444 if $ssh_config =~ /^Ciphers .*(blowfish|arcfour|3des).*$/m;
446 log_skip
("No SSH config file found.");
449 my $root_free = PVE
::Tools
::df
('/', 10);
450 log_warn
("Less than 2G free space on root file system.")
451 if defined($root_free) && $root_free->{avail
} < 2*1024*1024*1024;
453 log_info
("Checking for running guests..");
454 my $running_guests = 0;
456 my $vms = eval { PVE
::API2
::Qemu-
>vmlist({ node
=> $nodename }) };
457 log_warn
("Failed to retrieve information about this node's VMs - $@") if $@;
458 $running_guests += grep { $_->{status
} eq 'running' } @$vms if defined($vms);
460 my $cts = eval { PVE
::API2
::LXC-
>vmlist({ node
=> $nodename }) };
461 log_warn
("Failed to retrieve information about this node's CTs - $@") if $@;
462 $running_guests += grep { $_->{status
} eq 'running' } @$cts if defined($cts);
464 if ($running_guests > 0) {
465 log_warn
("$running_guests running guest(s) detected - consider migrating or stopping them.")
467 log_pass
("no running guest detected.")
470 log_info
("Checking if the local node's hostname is resolvable..");
471 my $host = PVE
::INotify
::nodename
();
472 my $local_ip = eval { PVE
::Network
::get_ip_from_hostname
($host) };
474 log_warn
("Failed to resolve hostname '$host' to IP - $@");
476 log_info
("Checking if resolved IP is configured on local node..");
477 my $cidr = Net
::IP
::ip_is_ipv6
($local_ip) ?
"$local_ip/128" : "$local_ip/32";
478 my $configured_ips = PVE
::Network
::get_local_ip_from_cidr
($cidr);
479 my $ip_count = scalar(@$configured_ips);
481 if ($ip_count <= 0) {
482 log_fail
("Resolved node IP '$local_ip' not configured or active for '$host'");
483 } elsif ($ip_count > 1) {
484 log_warn
("Resolved node IP '$local_ip' active on multiple ($ip_count) interfaces!");
486 log_pass
("Resolved node IP '$local_ip' configured and active on single interface.");
493 __PACKAGE__-
>register_method ({
497 description
=> 'Check (pre-/post-)upgrade conditions.',
499 additionalProperties
=> 0,
503 returns
=> { type
=> 'null' },
507 check_pve_packages
();
508 check_cluster_corosync
();
510 check_storage_health
();
513 print_header
("SUMMARY");
516 $total += $_ for values %$counters;
518 print "TOTAL: $total\n";
519 print colored
("PASSED: $counters->{pass}\n", 'green');
520 print "SKIPPED: $counters->{skip}\n";
521 print colored
("WARNINGS: $counters->{warn}\n", 'yellow');
522 print colored
("FAILURES: $counters->{fail}\n", 'red');
524 print colored
("\nATTENTION: Please check the output for detailed information!\n", 'red')
525 if ($counters->{warn} > 0 || $counters->{fail
} > 0);
530 our $cmddef = [ __PACKAGE__
, 'checklist', [], {}];
532 # for now drop all unknown params and just check