[pdm-devel] [PATCH datacenter-manager 3/3] api: remote shell: make websocket proxy a worker task
Fabian Grünbichler
f.gruenbichler at proxmox.com
Thu Dec 11 14:07:05 CET 2025
so that errors and warnings are visible to the user, at least in the task
list.. the request here is made by xterm.js as part of upgrading the connection
to a websocket, so returning the UPID in a meaningful fashion is hard..
Signed-off-by: Fabian Grünbichler <f.gruenbichler at proxmox.com>
---
Notes:
best viewed with `-w`, the actual change is quite small..
server/src/api/remote_shell.rs | 202 +++++++++++++++++----------------
ui/src/tasks.rs | 1 +
2 files changed, 103 insertions(+), 100 deletions(-)
diff --git a/server/src/api/remote_shell.rs b/server/src/api/remote_shell.rs
index 5207095..f1e1cab 100644
--- a/server/src/api/remote_shell.rs
+++ b/server/src/api/remote_shell.rs
@@ -158,122 +158,124 @@ fn upgrade_to_websocket(
let (mut ws, response) = WebSocket::new(parts.headers.clone())?;
- proxmox_rest_server::spawn_internal_task(async move {
- let incoming_ws: Upgraded =
- match hyper::upgrade::on(Request::from_parts(parts, req_body)).await {
- Ok(upgraded) => upgraded,
- Err(err) => bail!("failed to process incoming Websocket upgrade: {err}"),
+ // Can't return UPID because the response is the Websocket upgrade..
+ let _upid = proxmox_rest_server::WorkerTask::spawn(
+ "remote_shell",
+ None,
+ auth_id.to_string(),
+ true,
+ async move |_worker| {
+ let incoming_ws: Upgraded =
+ match hyper::upgrade::on(Request::from_parts(parts, req_body)).await {
+ Ok(upgraded) => upgraded,
+ Err(err) => bail!("failed to process incoming Websocket upgrade: {err}"),
+ };
+
+ let (remotes, _digest) = pdm_config::remotes::config()?;
+ let remote = get_remote(&remotes, &remote)?;
+ let (ticket, port, endpoint, first_api_url) = match remote.ty {
+ RemoteType::Pve => {
+ 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,
+ pve_api_types::NodeShellTermproxy {
+ cmd: None,
+ cmd_opts: None,
+ },
+ )
+ .await?;
+ (
+ 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,
+ None,
+ None,
+ )
+ }
};
- let (remotes, _digest) = pdm_config::remotes::config()?;
- let remote = get_remote(&remotes, &remote)?;
- let (ticket, port, endpoint, first_api_url) = match remote.ty {
- RemoteType::Pve => {
- 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,
- pve_api_types::NodeShellTermproxy {
- cmd: None,
- cmd_opts: None,
- },
- )
- .await?;
- (
- pve_term_ticket.ticket,
- pve_term_ticket.port,
- endpoint,
- Some(api_url),
- )
+ 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);
+
+ // 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..");
}
- 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,
- None,
- None,
- )
+ let api_url = api_url.into_parts();
+
+ let mut builder = http::uri::Builder::new();
+ if let Some(scheme) = api_url.scheme {
+ builder = builder.scheme(scheme);
}
- };
+ if let Some(authority) = api_url.authority {
+ builder = builder.authority(authority)
+ }
+ let api_path = ApiPathBuilder::new(format!("/api2/json/nodes/{node}/vncwebsocket"))
+ .arg("vncticket", ticket.clone())
+ .arg("port", port)
+ .build();
+ let uri = builder
+ .path_and_query(api_path)
+ .build()
+ .map_err(|err| format_err!("failed to build Uri - {err}"))?;
- 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 auth = raw_client.login_auth()?;
+ let req = Request::builder()
+ .method(Method::GET)
+ .uri(uri)
+ .header(UPGRADE, "websocket")
+ .header(SEC_WEBSOCKET_VERSION, "13")
+ .header(SEC_WEBSOCKET_KEY, ws_key);
- // 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 req = auth.set_auth_headers(req).body(Body::empty())?;
- let mut builder = http::uri::Builder::new();
- if let Some(scheme) = api_url.scheme {
- builder = builder.scheme(scheme);
- }
- if let Some(authority) = api_url.authority {
- builder = builder.authority(authority)
- }
- let api_path = ApiPathBuilder::new(format!("/api2/json/nodes/{node}/vncwebsocket"))
- .arg("vncticket", ticket.clone())
- .arg("port", port)
- .build();
- let uri = builder
- .path_and_query(api_path)
- .build()
- .map_err(|err| format_err!("failed to build Uri - {err}"))?;
+ let res = raw_client.http_client().request(req).await?;
+ if res.status() != StatusCode::SWITCHING_PROTOCOLS {
+ bail!("server didn't upgrade: {}", res.status());
+ }
- let auth = raw_client.login_auth()?;
- let req = Request::builder()
- .method(Method::GET)
- .uri(uri)
- .header(UPGRADE, "websocket")
- .header(SEC_WEBSOCKET_VERSION, "13")
- .header(SEC_WEBSOCKET_KEY, ws_key);
+ let pve_ws = hyper::upgrade::on(res)
+ .await
+ .map_err(|err| format_err!("failed to upgrade - {}", err))?;
- let req = auth.set_auth_headers(req).body(Body::empty())?;
+ let username = if let proxmox_client::AuthenticationKind::Token(ref token) = *auth {
+ token.userid.clone()
+ } else {
+ bail!("shell not supported with ticket-based authentication")
+ };
- let res = raw_client.http_client().request(req).await?;
- if res.status() != StatusCode::SWITCHING_PROTOCOLS {
- bail!("server didn't upgrade: {}", res.status());
- }
+ let preamble = format!("{username}:{ticket}\n", ticket = ticket);
+ ws.mask = Some([0, 0, 0, 0]);
- let pve_ws = hyper::upgrade::on(res)
- .await
- .map_err(|err| format_err!("failed to upgrade - {}", err))?;
-
- let username = if let proxmox_client::AuthenticationKind::Token(ref token) = *auth {
- token.userid.clone()
- } else {
- bail!("shell not supported with ticket-based authentication")
- };
-
- let preamble = format!("{username}:{ticket}\n", ticket = ticket);
- ws.mask = Some([0, 0, 0, 0]);
-
- if let Err(err) = ws
- .proxy_connection(
+ ws.proxy_connection(
TokioIo::new(incoming_ws),
TokioIo::new(pve_ws),
preamble.as_bytes(),
)
.await
- {
- log::warn!("error while copying between websockets: {err:?}");
- }
-
- Ok(())
- });
+ .map_err(|err| format_err!("error while copying between websockets: {err:?}"))
+ },
+ )?;
Ok(response)
}
diff --git a/ui/src/tasks.rs b/ui/src/tasks.rs
index 0f9a9aa..a644d5b 100644
--- a/ui/src/tasks.rs
+++ b/ui/src/tasks.rs
@@ -105,6 +105,7 @@ pub fn register_pve_tasks() {
register_task_description("vzumount", ("CT", tr!("Unmount")));
register_task_description("zfscreate", (tr!("ZFS Storage"), tr!("Create")));
register_task_description("zfsremove", ("ZFS Pool", tr!("Remove")));
+ register_task_description("remote_shell", tr!("Remote node shell"));
}
/// Format a UPID that is either [`RemoteUpid`] or a [`UPID`]
--
2.47.3
More information about the pdm-devel
mailing list