all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Kefu Chai <k.chai@proxmox.com>
To: pve-devel@lists.proxmox.com
Cc: Thomas Lamprecht <t.lamprecht@proxmox.com>,
	Jonas Theisen <j.theisen@proxmox.com>
Subject: [PATCH manager v2 2/4] migrations: cephx: judge cipher support by the version a daemon runs
Date: Mon, 19 Oct 2026 16:23:44 +0800	[thread overview]
Message-ID: <20261019082346.1781960-3-k.chai@proxmox.com> (raw)
In-Reply-To: <20261019082346.1781960-1-k.chai@proxmox.com>

The cipher check needs both the running and the installed version of a
daemon. Only pvestatd reports the installed version, and only on our
nodes. So a daemon on another host always failed the check, and a
stretch cluster with an external tiebreaker could not migrate.

For such a daemon, check only the running version and warn that Ceph on
that host must not be downgraded. A stopped daemon still fails the
check, as nothing reports its version.

Signed-off-by: Kefu Chai <k.chai@proxmox.com>
---
 bin/pve-cephx-rotate-service-keys   | 47 ++++++++++++++++----
 test/CephKeyMigrationScript_test.pl | 66 +++++++++++++++++++++++++++++
 2 files changed, 104 insertions(+), 9 deletions(-)

diff --git a/bin/pve-cephx-rotate-service-keys b/bin/pve-cephx-rotate-service-keys
index 84daff6d..8048944b 100755
--- a/bin/pve-cephx-rotate-service-keys
+++ b/bin/pve-cephx-rotate-service-keys
@@ -189,9 +189,13 @@ my sub daemon_location($type, $id, $host) {
     };
 }
 
-my sub assert_member($node, $entity) {
+my sub assert_located($node, $entity) {
     die "Cannot manage '$entity': no valid host name was reported\n"
         if !defined($node) || ref($node) || !length($node);
+}
+
+my sub assert_member($node, $entity) {
+    assert_located($node, $entity);
     die "Cannot manage '$entity' on '$node', which is not a node of this Proxmox VE cluster\n"
         if !cluster_nodes()->{$node};
 }
@@ -2545,31 +2549,49 @@ my sub preflight_nodes($info, $plan, $opts) {
             (@touched, grep { defined($_->{node}) && $lockbox_nodes->{ $_->{node} } } @all);
     }
 
-    my (@outdated, @unknown_versions);
+    # The running version tells whether a daemon can use the new cipher now, and Ceph reports it for
+    # every daemon. The installed version tells whether it still can after a restart, and only
+    # pvestatd reports it, for our nodes. A daemon elsewhere is checked by its running version only,
+    # so it has to be running.
+    my (@outdated, @unknown_versions, @running_only);
     for my $daemon (@judged) {
-        if (!eval { assert_member($daemon->{node}, "$daemon->{type}.$daemon->{id}"); 1 }) {
+        my $label = "$daemon->{type}.$daemon->{id}";
+        my $stopped = ($daemon->{recovered} || $daemon->{down}) ? 1 : 0;
+
+        if (!eval { assert_located($daemon->{node}, $label); 1 }) {
             push @unknown_versions, $@;
             next;
         }
-        if (!$daemon->{recovered} && !$daemon->{down}) {
+        if (!$daemon->{managed} && $stopped) {
+            push @unknown_versions,
+                "cannot verify the Ceph version of $label: it does not run, and its host"
+                . " '$daemon->{node}' is not a node of this Proxmox VE cluster";
+            next;
+        }
+
+        if (!$stopped) {
             if (!defined($daemon->{version}) || $daemon->{version} eq '') {
                 push @unknown_versions,
-                    "could not verify the running Ceph version of $daemon->{entity} on node"
+                    "could not verify the running Ceph version of $label on node"
                     . " '$daemon->{node}'";
             } elsif (!version_has_cipher($daemon->{version})) {
                 push @outdated,
-                    "$daemon->{entity} on node '$daemon->{node}' runs "
-                    . short_version($daemon->{version});
+                    "$label on node '$daemon->{node}' runs " . short_version($daemon->{version});
+            } elsif (!$daemon->{managed}) {
+                push @running_only,
+                    "$label on '$daemon->{node}' runs " . short_version($daemon->{version});
             }
         }
 
+        next if !$daemon->{managed};
+
         if (!defined($daemon->{binary}) || $daemon->{binary} eq '') {
             push @unknown_versions,
-                "could not verify the installed Ceph version of $daemon->{entity} on node"
+                "could not verify the installed Ceph version of $label on node"
                 . " '$daemon->{node}'; check that 'pvestatd' runs there";
         } elsif (!version_has_cipher($daemon->{binary})) {
             push @outdated,
-                "$daemon->{entity} on node '$daemon->{node}' would restart into "
+                "$label on node '$daemon->{node}' would restart into "
                 . short_version($daemon->{binary});
         }
     }
@@ -2636,6 +2658,13 @@ my sub preflight_nodes($info, $plan, $opts) {
         }
     }
 
+    if (@running_only) {
+        log_warn("These daemons run on hosts this cluster does not manage, so only the version they"
+            . " run was checked, not the one installed there. Keep Ceph on those hosts at this"
+            . " version or newer, or they lose the new cipher on their next restart:");
+        log_steps(\@running_only);
+    }
+
     if (@unknown_versions) {
         log_fail("Could not verify '$CIPHER' support for every service daemon:");
         log_steps(\@unknown_versions);
diff --git a/test/CephKeyMigrationScript_test.pl b/test/CephKeyMigrationScript_test.pl
index fc9811d7..6ccadbc4 100755
--- a/test/CephKeyMigrationScript_test.pl
+++ b/test/CephKeyMigrationScript_test.pl
@@ -6171,6 +6171,7 @@ for my $case ([0, 0], [1, 0], [0, 1], [1, 1]) {
                 id => "$_",
                 entity => "mon.$_",
                 node => "node$_",
+                managed => 1,
                 store => 'file',
                 version => '20.2.4-pve4',
                 binary => '20.2.4-pve4',
@@ -8030,4 +8031,69 @@ for my $case ([0, 0], [1, 0], [0, 1], [1, 1]) {
     );
 }
 
+# a daemon outside the cluster has no installed version, so only its running version counts
+{
+    my $info = sub {
+        my (%tiebreaker) = @_;
+        my $mons = [
+            (
+                map { {
+                    type => 'mon',
+                    id => "$_",
+                    entity => "mon.$_",
+                    node => "node$_",
+                    managed => 1,
+                    store => 'file',
+                    version => '20.2.4-pve4',
+                    binary => '20.2.4-pve4',
+                } } 1 .. 2
+            ),
+            {
+                type => 'mon',
+                id => 'tiebreaker',
+                # all monitors share the 'mon.' key, as the inventory records
+                entity => 'mon.',
+                node => 'tiebreaker.invalid',
+                managed => 0,
+                store => 'file',
+                version => '20.2.4-pve4',
+                %tiebreaker,
+            },
+        ];
+        return {
+            daemons => { mon => $mons, mgr => [], mds => [], osd => [] },
+            monmap_mons => [map { $_->{id} } @$mons],
+            rados => MonCountRados->new(),
+        };
+    };
+    my $preflight = sub {
+        my ($info) = @_;
+        my ($output, $verdict) = ('');
+        {
+            local *STDOUT;
+            open(STDOUT, '>', \$output) or die $!;
+            $verdict = $HOOKS->{preflight_nodes}
+                ->($info, { daemons => [], lockbox_keys => [], service_cipher => 1 }, {});
+        }
+        return ($verdict, $output // '');
+    };
+
+    my ($verdict, $output) = $preflight->($info->());
+    is($verdict, 1, 'a capable monitor outside the cluster does not block the cipher switch');
+    like(
+        $output,
+        qr/does not manage.*mon\.tiebreaker on 'tiebreaker\.invalid' runs 20\.2\.4/s,
+        'naming it with the version it was judged by',
+    );
+
+    ($verdict, $output) = $preflight->($info->(version => '19.2.5'));
+    is($verdict, -1, 'an outdated monitor outside the cluster still blocks');
+    like($output, qr/mon\.tiebreaker on node 'tiebreaker\.invalid' runs 19\.2\.5/, 'as outdated');
+
+    ($verdict, $output) = $preflight->($info->(down => 1));
+    is($verdict, -1, 'a stopped monitor outside the cluster cannot be judged');
+    like($output, qr/mon\.tiebreaker.*not a node/, 'which names the membership');
+
+}
+
 done_testing();
-- 
2.47.3





  parent reply	other threads:[~2026-10-19  8:24 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-19  8:23 [PATCH manager v2 0/4] cephx: migrate keys with a monitor this cluster does not manage Kefu Chai
2026-10-19  8:23 ` [PATCH manager v2 1/4] migrations: cephx: ask a monitor about itself through one route Kefu Chai
2026-10-19  8:23 ` Kefu Chai [this message]
2026-10-19  8:23 ` [PATCH manager v2 3/4] migrations: cephx: rotate the monitor key with monitors we do not manage Kefu Chai
2026-10-19  8:23 ` [PATCH manager v2 4/4] migrations: cephx: name the hosts keeping key copies we cannot write Kefu Chai
2026-10-19 14:47 ` [PATCH manager v2 0/4] cephx: migrate keys with a monitor this cluster does not manage Jonas Theisen
2026-10-19 18:23 ` applied: " Thomas Lamprecht

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261019082346.1781960-3-k.chai@proxmox.com \
    --to=k.chai@proxmox.com \
    --cc=j.theisen@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    --cc=t.lamprecht@proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal