public inbox for pdm-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: "Fabian Grünbichler" <f.gruenbichler@proxmox.com>
To: pdm-devel@lists.proxmox.com
Subject: [pdm-devel] [PATCH datacenter-manager 1/3] api: remote shell: do not rely on first node being reachable
Date: Thu, 11 Dec 2025 14:07:03 +0100	[thread overview]
Message-ID: <20251211130710.2071983-2-f.gruenbichler@proxmox.com> (raw)
In-Reply-To: <20251211130710.2071983-1-f.gruenbichler@proxmox.com>

currently, there is no way to get to the raw, underlying HTTP or API client
from a PVEClient, and the PVEClient does not support doing the Websocket
upgrade.

by extending the "raw_client" to allow picking a specific endpoint, the remote
shell no longer relies on the first node of a remote being available, if the
target node is directly reachable from the PDM instance. this also fixes an
issue where the `termproxy` request via a regular PVEClient, and the
`vncwebsocket` request via the raw client ended up talking to two different
nodes, which does not work, since neither of those endpoints are proxied to the
target node on the PVE side.

this is just a stop gap measure, ideally we find a way to re-use the
MultiClient for PVE targets for the remote shell.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
---
 server/src/api/remote_shell.rs                | 37 +++++++++++++++----
 server/src/connection.rs                      | 10 ++---
 .../src/metric_collection/collection_task.rs  |  6 ++-
 3 files changed, 40 insertions(+), 13 deletions(-)

diff --git a/server/src/api/remote_shell.rs b/server/src/api/remote_shell.rs
index c617b4d..d5e43e9 100644
--- a/server/src/api/remote_shell.rs
+++ b/server/src/api/remote_shell.rs
@@ -7,6 +7,7 @@ use http::{
 };
 use hyper::upgrade::Upgraded;
 use hyper_util::rt::TokioIo;
+use pve_api_types::client::{PveClient, PveClientImpl};
 use serde_json::{json, Value};
 
 use proxmox_auth_api::{
@@ -169,9 +170,17 @@ fn upgrade_to_websocket(
 
             let (remotes, _digest) = pdm_config::remotes::config()?;
             let remote = get_remote(&remotes, &remote)?;
-            let (ticket, port) = match remote.ty {
+            let (ticket, port, endpoint, first_api_url) = match remote.ty {
                 RemoteType::Pve => {
-                    let pve = crate::connection::make_pve_client(&remote)?;
+                    let cache = crate::remote_cache::RemoteMappingCache::get();
+                    let endpoint = match cache.info_by_node_name(&remote.id, &node) {
+                        Some(info) if info.reachable => Some(info.hostname.clone()),
+                        _ => None,
+                    };
+                    let raw_client =
+                        crate::connection::make_raw_client(remote, endpoint.as_deref())?;
+                    let api_url = raw_client.api_url().clone();
+                    let pve = PveClientImpl(*raw_client);
                     let pve_term_ticket = pve
                         .node_shell_termproxy(
                             &node,
@@ -181,21 +190,35 @@ fn upgrade_to_websocket(
                             },
                         )
                         .await?;
-                    (pve_term_ticket.ticket, pve_term_ticket.port)
+                    (
+                        pve_term_ticket.ticket,
+                        pve_term_ticket.port,
+                        endpoint,
+                        Some(api_url),
+                    )
                 }
                 RemoteType::Pbs => {
                     let pbs = crate::connection::make_pbs_client(&remote)?;
                     let pbs_term_ticket = pbs.node_shell_termproxy().await?;
-                    (pbs_term_ticket.ticket, pbs_term_ticket.port as i64)
+                    (
+                        pbs_term_ticket.ticket,
+                        pbs_term_ticket.port as i64,
+                        None,
+                        None,
+                    )
                 }
             };
 
-            let raw_client = crate::connection::make_raw_client(remote)?;
-
+            let raw_client = crate::connection::make_raw_client(remote, endpoint.as_deref())?;
             let ws_key = proxmox_sys::linux::random_data(16)?;
             let ws_key = proxmox_base64::encode(&ws_key);
 
-            let api_url = raw_client.api_url().clone().into_parts();
+            // ensure request above and below end up at the same node
+            let api_url = raw_client.api_url().clone();
+            if first_api_url.is_some() && first_api_url.as_ref() != Some(&api_url) {
+                bail!("termproxy and vncwebsocket API calls must be made to the same node..");
+            }
+            let api_url = api_url.into_parts();
 
             let mut builder = http::uri::Builder::new();
             if let Some(scheme) = api_url.scheme {
diff --git a/server/src/connection.rs b/server/src/connection.rs
index 7e36671..efb5a1d 100644
--- a/server/src/connection.rs
+++ b/server/src/connection.rs
@@ -258,7 +258,7 @@ pub trait ClientFactory {
     async fn make_pbs_client_and_login(&self, remote: &Remote) -> Result<Box<PbsClient>, Error>;
 
     /// Create a new API client for raw acess to the given remote
-    fn make_raw_client(&self, remote: &Remote) -> Result<Box<Client>, Error>;
+    fn make_raw_client(&self, remote: &Remote, node: Option<&str>) -> Result<Box<Client>, Error>;
 }
 
 /// Default production client factory
@@ -353,8 +353,8 @@ impl ClientFactory for DefaultClientFactory {
         ConnectionCache::get().make_pve_client(remote)
     }
 
-    fn make_raw_client(&self, remote: &Remote) -> Result<Box<Client>, Error> {
-        Ok(Box::new(crate::connection::connect(remote, None)?))
+    fn make_raw_client(&self, remote: &Remote, node: Option<&str>) -> Result<Box<Client>, Error> {
+        Ok(Box::new(crate::connection::connect(remote, node)?))
     }
 
     fn make_pbs_client(&self, remote: &Remote) -> Result<Box<PbsClient>, Error> {
@@ -429,8 +429,8 @@ pub fn make_pbs_client(remote: &Remote) -> Result<Box<PbsClient>, Error> {
     instance().make_pbs_client(remote)
 }
 
-pub fn make_raw_client(remote: &Remote) -> Result<Box<Client>, Error> {
-    instance().make_raw_client(remote)
+pub fn make_raw_client(remote: &Remote, node: Option<&str>) -> Result<Box<Client>, Error> {
+    instance().make_raw_client(remote, node)
 }
 
 /// Create a new API client for PVE remotes.
diff --git a/server/src/metric_collection/collection_task.rs b/server/src/metric_collection/collection_task.rs
index cc1a460..7da85de 100644
--- a/server/src/metric_collection/collection_task.rs
+++ b/server/src/metric_collection/collection_task.rs
@@ -431,7 +431,11 @@ pub(super) mod tests {
             bail!("not implemented")
         }
 
-        fn make_raw_client(&self, _remote: &Remote) -> Result<Box<Client>, Error> {
+        fn make_raw_client(
+            &self,
+            _remote: &Remote,
+            _node: Option<&str>,
+        ) -> Result<Box<Client>, Error> {
             bail!("not implemented")
         }
 
-- 
2.47.3



_______________________________________________
pdm-devel mailing list
pdm-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel

  reply	other threads:[~2025-12-11 13:07 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-12-11 13:07 [pdm-devel] [PATCH datacenter-manager 0/3] improve node shell Fabian Grünbichler
2025-12-11 13:07 ` Fabian Grünbichler [this message]
2025-12-11 13:07 ` [pdm-devel] [PATCH datacenter-manager 2/3] api: remote shell: improve error message Fabian Grünbichler
2025-12-11 13:07 ` [pdm-devel] [PATCH datacenter-manager 3/3] api: remote shell: make websocket proxy a worker task Fabian Grünbichler

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=20251211130710.2071983-2-f.gruenbichler@proxmox.com \
    --to=f.gruenbichler@proxmox.com \
    --cc=pdm-devel@lists.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal