rest server: rust fmt
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
parent
b300e6fbc2
commit
41583796b1
|
@ -1,18 +1,20 @@
|
||||||
use std::sync::Mutex;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use anyhow::{bail, format_err, Error};
|
use anyhow::{bail, format_err, Error};
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use hyper::{Body, Response, Method};
|
|
||||||
use http::request::Parts;
|
use http::request::Parts;
|
||||||
use http::HeaderMap;
|
use http::HeaderMap;
|
||||||
|
use hyper::{Body, Method, Response};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
use proxmox_router::{
|
||||||
|
list_subdirs_api_method, Router, RpcEnvironmentType, SubdirMap, UserInformation,
|
||||||
|
};
|
||||||
use proxmox_schema::api;
|
use proxmox_schema::api;
|
||||||
use proxmox_router::{list_subdirs_api_method, SubdirMap, Router, RpcEnvironmentType, UserInformation};
|
|
||||||
|
|
||||||
use proxmox_rest_server::{ServerAdapter, ApiConfig, AuthError, RestServer, RestEnvironment};
|
use proxmox_rest_server::{ApiConfig, AuthError, RestEnvironment, RestServer, ServerAdapter};
|
||||||
|
|
||||||
// Create a Dummy User information system
|
// Create a Dummy User information system
|
||||||
struct DummyUserInfo;
|
struct DummyUserInfo;
|
||||||
|
@ -34,13 +36,17 @@ struct MinimalServer;
|
||||||
|
|
||||||
// implement the server adapter
|
// implement the server adapter
|
||||||
impl ServerAdapter for MinimalServer {
|
impl ServerAdapter for MinimalServer {
|
||||||
|
|
||||||
// normally this would check and authenticate the user
|
// normally this would check and authenticate the user
|
||||||
fn check_auth(
|
fn check_auth(
|
||||||
&self,
|
&self,
|
||||||
_headers: &HeaderMap,
|
_headers: &HeaderMap,
|
||||||
_method: &Method,
|
_method: &Method,
|
||||||
) -> Pin<Box<dyn Future<Output = Result<(String, Box<dyn UserInformation + Sync + Send>), AuthError>> + Send>> {
|
) -> Pin<
|
||||||
|
Box<
|
||||||
|
dyn Future<Output = Result<(String, Box<dyn UserInformation + Sync + Send>), AuthError>>
|
||||||
|
+ Send,
|
||||||
|
>,
|
||||||
|
> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
// get some global/cached userinfo
|
// get some global/cached userinfo
|
||||||
let userinfo: Box<dyn UserInformation + Sync + Send> = Box::new(DummyUserInfo);
|
let userinfo: Box<dyn UserInformation + Sync + Send> = Box::new(DummyUserInfo);
|
||||||
|
@ -121,7 +127,12 @@ fn create_item(name: String, value: String) -> Result<(), Error> {
|
||||||
)]
|
)]
|
||||||
/// returns the value of an item
|
/// returns the value of an item
|
||||||
fn get_item(name: String) -> Result<String, Error> {
|
fn get_item(name: String) -> Result<String, Error> {
|
||||||
ITEM_MAP.lock().unwrap().get(&name).map(|s| s.to_string()).ok_or_else(|| format_err!("no such item '{}'", name))
|
ITEM_MAP
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.get(&name)
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.ok_or_else(|| format_err!("no such item '{}'", name))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[api(
|
#[api(
|
||||||
|
@ -177,13 +188,9 @@ const SUBDIRS: SubdirMap = &[
|
||||||
&Router::new()
|
&Router::new()
|
||||||
.get(&API_METHOD_LIST_ITEMS)
|
.get(&API_METHOD_LIST_ITEMS)
|
||||||
.post(&API_METHOD_CREATE_ITEM)
|
.post(&API_METHOD_CREATE_ITEM)
|
||||||
.match_all("name", &ITEM_ROUTER)
|
.match_all("name", &ITEM_ROUTER),
|
||||||
),
|
|
||||||
(
|
|
||||||
"ping",
|
|
||||||
&Router::new()
|
|
||||||
.get(&API_METHOD_PING)
|
|
||||||
),
|
),
|
||||||
|
("ping", &Router::new().get(&API_METHOD_PING)),
|
||||||
];
|
];
|
||||||
|
|
||||||
const ROUTER: Router = Router::new()
|
const ROUTER: Router = Router::new()
|
||||||
|
@ -191,7 +198,6 @@ const ROUTER: Router = Router::new()
|
||||||
.subdirs(SUBDIRS);
|
.subdirs(SUBDIRS);
|
||||||
|
|
||||||
async fn run() -> Result<(), Error> {
|
async fn run() -> Result<(), Error> {
|
||||||
|
|
||||||
// we first have to configure the api environment (basedir etc.)
|
// we first have to configure the api environment (basedir etc.)
|
||||||
|
|
||||||
let config = ApiConfig::new(
|
let config = ApiConfig::new(
|
||||||
|
@ -204,21 +210,16 @@ async fn run() -> Result<(), Error> {
|
||||||
|
|
||||||
// then we have to create a daemon that listens, accepts and serves
|
// then we have to create a daemon that listens, accepts and serves
|
||||||
// the api to clients
|
// the api to clients
|
||||||
proxmox_rest_server::daemon::create_daemon(
|
proxmox_rest_server::daemon::create_daemon(([127, 0, 0, 1], 65000).into(), move |listener| {
|
||||||
([127, 0, 0, 1], 65000).into(),
|
|
||||||
move |listener| {
|
|
||||||
let incoming = hyper::server::conn::AddrIncoming::from_listener(listener)?;
|
let incoming = hyper::server::conn::AddrIncoming::from_listener(listener)?;
|
||||||
|
|
||||||
Ok(async move {
|
Ok(async move {
|
||||||
|
hyper::Server::builder(incoming).serve(rest_server).await?;
|
||||||
hyper::Server::builder(incoming)
|
|
||||||
.serve(rest_server)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
},
|
})
|
||||||
).await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,21 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::time::SystemTime;
|
|
||||||
use std::fs::metadata;
|
use std::fs::metadata;
|
||||||
use std::sync::{Arc, Mutex, RwLock};
|
use std::path::PathBuf;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
use std::sync::{Arc, Mutex, RwLock};
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use anyhow::{bail, Error, format_err};
|
use anyhow::{bail, format_err, Error};
|
||||||
use hyper::{Method, Body, Response};
|
|
||||||
use hyper::http::request::Parts;
|
use hyper::http::request::Parts;
|
||||||
|
use hyper::{Body, Method, Response};
|
||||||
|
|
||||||
use handlebars::Handlebars;
|
use handlebars::Handlebars;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use proxmox_sys::fs::{create_path, CreateOptions};
|
|
||||||
use proxmox_router::{ApiMethod, Router, RpcEnvironmentType, UserInformation};
|
use proxmox_router::{ApiMethod, Router, RpcEnvironmentType, UserInformation};
|
||||||
|
use proxmox_sys::fs::{create_path, CreateOptions};
|
||||||
|
|
||||||
use crate::{ServerAdapter, AuthError, FileLogger, FileLogOptions, CommandSocket, RestEnvironment};
|
use crate::{AuthError, CommandSocket, FileLogOptions, FileLogger, RestEnvironment, ServerAdapter};
|
||||||
|
|
||||||
|
|
||||||
/// REST server configuration
|
/// REST server configuration
|
||||||
pub struct ApiConfig {
|
pub struct ApiConfig {
|
||||||
|
@ -87,12 +86,10 @@ impl ApiConfig {
|
||||||
method: Method,
|
method: Method,
|
||||||
uri_param: &mut HashMap<String, String>,
|
uri_param: &mut HashMap<String, String>,
|
||||||
) -> Option<&'static ApiMethod> {
|
) -> Option<&'static ApiMethod> {
|
||||||
|
|
||||||
self.router.find_method(components, method, uri_param)
|
self.router.find_method(components, method, uri_param)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn find_alias(&self, components: &[&str]) -> PathBuf {
|
pub(crate) fn find_alias(&self, components: &[&str]) -> PathBuf {
|
||||||
|
|
||||||
let mut prefix = String::new();
|
let mut prefix = String::new();
|
||||||
let mut filename = self.basedir.clone();
|
let mut filename = self.basedir.clone();
|
||||||
let comp_len = components.len();
|
let comp_len = components.len();
|
||||||
|
@ -100,7 +97,10 @@ impl ApiConfig {
|
||||||
prefix.push_str(components[0]);
|
prefix.push_str(components[0]);
|
||||||
if let Some(subdir) = self.aliases.get(&prefix) {
|
if let Some(subdir) = self.aliases.get(&prefix) {
|
||||||
filename.push(subdir);
|
filename.push(subdir);
|
||||||
components.iter().skip(1).for_each(|comp| filename.push(comp));
|
components
|
||||||
|
.iter()
|
||||||
|
.skip(1)
|
||||||
|
.for_each(|comp| filename.push(comp));
|
||||||
} else {
|
} else {
|
||||||
components.iter().for_each(|comp| filename.push(comp));
|
components.iter().for_each(|comp| filename.push(comp));
|
||||||
}
|
}
|
||||||
|
@ -121,7 +121,8 @@ impl ApiConfig {
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn add_alias<S, P>(&mut self, alias: S, path: P)
|
pub fn add_alias<S, P>(&mut self, alias: S, path: P)
|
||||||
where S: Into<String>,
|
where
|
||||||
|
S: Into<String>,
|
||||||
P: Into<PathBuf>,
|
P: Into<PathBuf>,
|
||||||
{
|
{
|
||||||
self.aliases.insert(alias.into(), path.into());
|
self.aliases.insert(alias.into(), path.into());
|
||||||
|
@ -136,7 +137,7 @@ impl ApiConfig {
|
||||||
/// Those templates cane be use with [render_template](Self::render_template) to generate pages.
|
/// Those templates cane be use with [render_template](Self::render_template) to generate pages.
|
||||||
pub fn register_template<P>(&self, name: &str, path: P) -> Result<(), Error>
|
pub fn register_template<P>(&self, name: &str, path: P) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
P: Into<PathBuf>
|
P: Into<PathBuf>,
|
||||||
{
|
{
|
||||||
if self.template_files.read().unwrap().contains_key(name) {
|
if self.template_files.read().unwrap().contains_key(name) {
|
||||||
bail!("template already registered");
|
bail!("template already registered");
|
||||||
|
@ -146,8 +147,14 @@ impl ApiConfig {
|
||||||
let metadata = metadata(&path)?;
|
let metadata = metadata(&path)?;
|
||||||
let mtime = metadata.modified()?;
|
let mtime = metadata.modified()?;
|
||||||
|
|
||||||
self.templates.write().unwrap().register_template_file(name, &path)?;
|
self.templates
|
||||||
self.template_files.write().unwrap().insert(name.to_string(), (mtime, path));
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.register_template_file(name, &path)?;
|
||||||
|
self.template_files
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.insert(name.to_string(), (mtime, path));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -162,11 +169,18 @@ impl ApiConfig {
|
||||||
let mtime;
|
let mtime;
|
||||||
{
|
{
|
||||||
let template_files = self.template_files.read().unwrap();
|
let template_files = self.template_files.read().unwrap();
|
||||||
let (old_mtime, old_path) = template_files.get(name).ok_or_else(|| format_err!("template not found"))?;
|
let (old_mtime, old_path) = template_files
|
||||||
|
.get(name)
|
||||||
|
.ok_or_else(|| format_err!("template not found"))?;
|
||||||
|
|
||||||
mtime = metadata(old_path)?.modified()?;
|
mtime = metadata(old_path)?.modified()?;
|
||||||
if mtime <= *old_mtime {
|
if mtime <= *old_mtime {
|
||||||
return self.templates.read().unwrap().render(name, data).map_err(|err| format_err!("{}", err));
|
return self
|
||||||
|
.templates
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.render(name, data)
|
||||||
|
.map_err(|err| format_err!("{}", err));
|
||||||
}
|
}
|
||||||
path = old_path.to_path_buf();
|
path = old_path.to_path_buf();
|
||||||
}
|
}
|
||||||
|
@ -178,7 +192,9 @@ impl ApiConfig {
|
||||||
templates.register_template_file(name, &path)?;
|
templates.register_template_file(name, &path)?;
|
||||||
template_files.insert(name.to_string(), (mtime, path));
|
template_files.insert(name.to_string(), (mtime, path));
|
||||||
|
|
||||||
templates.render(name, data).map_err(|err| format_err!("{}", err))
|
templates
|
||||||
|
.render(name, data)
|
||||||
|
.map_err(|err| format_err!("{}", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,7 +211,7 @@ impl ApiConfig {
|
||||||
commando_sock: &mut CommandSocket,
|
commando_sock: &mut CommandSocket,
|
||||||
) -> Result<(), Error>
|
) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
P: Into<PathBuf>
|
P: Into<PathBuf>,
|
||||||
{
|
{
|
||||||
let path: PathBuf = path.into();
|
let path: PathBuf = path.into();
|
||||||
if let Some(base) = path.parent() {
|
if let Some(base) = path.parent() {
|
||||||
|
@ -234,7 +250,7 @@ impl ApiConfig {
|
||||||
commando_sock: &mut CommandSocket,
|
commando_sock: &mut CommandSocket,
|
||||||
) -> Result<(), Error>
|
) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
P: Into<PathBuf>
|
P: Into<PathBuf>,
|
||||||
{
|
{
|
||||||
let path: PathBuf = path.into();
|
let path: PathBuf = path.into();
|
||||||
if let Some(base) = path.parent() {
|
if let Some(base) = path.parent() {
|
||||||
|
|
|
@ -2,18 +2,22 @@ use anyhow::{bail, format_err, Error};
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::os::unix::io::AsRawFd;
|
use std::os::unix::io::AsRawFd;
|
||||||
use std::path::{PathBuf, Path};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use futures::*;
|
use futures::*;
|
||||||
use tokio::net::UnixListener;
|
|
||||||
use serde::Serialize;
|
|
||||||
use serde_json::Value;
|
|
||||||
use nix::sys::socket;
|
use nix::sys::socket;
|
||||||
use nix::unistd::Gid;
|
use nix::unistd::Gid;
|
||||||
|
use serde::Serialize;
|
||||||
|
use serde_json::Value;
|
||||||
|
use tokio::net::UnixListener;
|
||||||
|
|
||||||
// Listens on a Unix Socket to handle simple command asynchronously
|
// Listens on a Unix Socket to handle simple command asynchronously
|
||||||
fn create_control_socket<P, F>(path: P, gid: Gid, func: F) -> Result<impl Future<Output = ()>, Error>
|
fn create_control_socket<P, F>(
|
||||||
|
path: P,
|
||||||
|
gid: Gid,
|
||||||
|
func: F,
|
||||||
|
) -> Result<impl Future<Output = ()>, Error>
|
||||||
where
|
where
|
||||||
P: Into<PathBuf>,
|
P: Into<PathBuf>,
|
||||||
F: Fn(Value) -> Result<Value, Error> + Send + Sync + 'static,
|
F: Fn(Value) -> Result<Value, Error> + Send + Sync + 'static,
|
||||||
|
@ -59,13 +63,20 @@ where
|
||||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncBufReadExt, AsyncWriteExt};
|
||||||
let func = Arc::clone(&func);
|
let func = Arc::clone(&func);
|
||||||
let path = path.clone();
|
let path = path.clone();
|
||||||
tokio::spawn(futures::future::select(
|
tokio::spawn(
|
||||||
|
futures::future::select(
|
||||||
async move {
|
async move {
|
||||||
let mut rx = tokio::io::BufReader::new(rx);
|
let mut rx = tokio::io::BufReader::new(rx);
|
||||||
let mut line = String::new();
|
let mut line = String::new();
|
||||||
loop {
|
loop {
|
||||||
line.clear();
|
line.clear();
|
||||||
match rx.read_line({ line.clear(); &mut line }).await {
|
match rx
|
||||||
|
.read_line({
|
||||||
|
line.clear();
|
||||||
|
&mut line
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(0) => break,
|
Ok(0) => break,
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
@ -78,26 +89,31 @@ where
|
||||||
Ok(param) => match func(param) {
|
Ok(param) => match func(param) {
|
||||||
Ok(res) => format!("OK: {}\n", res),
|
Ok(res) => format!("OK: {}\n", res),
|
||||||
Err(err) => format!("ERROR: {}\n", err),
|
Err(err) => format!("ERROR: {}\n", err),
|
||||||
}
|
},
|
||||||
Err(err) => format!("ERROR: {}\n", err),
|
Err(err) => format!("ERROR: {}\n", err),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(err) = tx.write_all(response.as_bytes()).await {
|
if let Err(err) = tx.write_all(response.as_bytes()).await {
|
||||||
eprintln!("control socket {:?} write response error: {}", path, err);
|
eprintln!(
|
||||||
|
"control socket {:?} write response error: {}",
|
||||||
|
path, err
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.boxed(),
|
|
||||||
abort_future,
|
|
||||||
).map(|_| ()));
|
|
||||||
}
|
}
|
||||||
}.boxed();
|
.boxed(),
|
||||||
|
abort_future,
|
||||||
|
)
|
||||||
|
.map(|_| ()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.boxed();
|
||||||
|
|
||||||
let abort_future = crate::last_worker_future().map_err(|_| {});
|
let abort_future = crate::last_worker_future().map_err(|_| {});
|
||||||
let task = futures::future::select(
|
let task = futures::future::select(control_future, abort_future)
|
||||||
control_future,
|
.map(|_: futures::future::Either<(Result<(), Error>, _), _>| ());
|
||||||
abort_future,
|
|
||||||
).map(|_: futures::future::Either<(Result<(), Error>, _), _>| ());
|
|
||||||
|
|
||||||
Ok(task)
|
Ok(task)
|
||||||
}
|
}
|
||||||
|
@ -148,7 +164,8 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
// A callback for a specific commando socket.
|
// A callback for a specific commando socket.
|
||||||
type CommandSocketFn = Box<(dyn Fn(Option<&Value>) -> Result<Value, Error> + Send + Sync + 'static)>;
|
type CommandSocketFn =
|
||||||
|
Box<(dyn Fn(Option<&Value>) -> Result<Value, Error> + Send + Sync + 'static)>;
|
||||||
|
|
||||||
/// Tooling to get a single control command socket where one can
|
/// Tooling to get a single control command socket where one can
|
||||||
/// register multiple commands dynamically.
|
/// register multiple commands dynamically.
|
||||||
|
@ -164,7 +181,8 @@ pub struct CommandSocket {
|
||||||
impl CommandSocket {
|
impl CommandSocket {
|
||||||
/// Creates a new instance.
|
/// Creates a new instance.
|
||||||
pub fn new<P>(path: P, gid: Gid) -> Self
|
pub fn new<P>(path: P, gid: Gid) -> Self
|
||||||
where P: Into<PathBuf>,
|
where
|
||||||
|
P: Into<PathBuf>,
|
||||||
{
|
{
|
||||||
CommandSocket {
|
CommandSocket {
|
||||||
socket: path.into(),
|
socket: path.into(),
|
||||||
|
@ -176,10 +194,11 @@ impl CommandSocket {
|
||||||
/// Spawn the socket and consume self, meaning you cannot register commands anymore after
|
/// Spawn the socket and consume self, meaning you cannot register commands anymore after
|
||||||
/// calling this.
|
/// calling this.
|
||||||
pub fn spawn(self) -> Result<(), Error> {
|
pub fn spawn(self) -> Result<(), Error> {
|
||||||
let control_future = create_control_socket(self.socket.to_owned(), self.gid, move |param| {
|
let control_future =
|
||||||
let param = param
|
create_control_socket(self.socket.to_owned(), self.gid, move |param| {
|
||||||
.as_object()
|
let param = param.as_object().ok_or_else(|| {
|
||||||
.ok_or_else(|| format_err!("unable to parse parameters (expected json object)"))?;
|
format_err!("unable to parse parameters (expected json object)")
|
||||||
|
})?;
|
||||||
|
|
||||||
let command = match param.get("command") {
|
let command = match param.get("command") {
|
||||||
Some(Value::String(command)) => command.as_str(),
|
Some(Value::String(command)) => command.as_str(),
|
||||||
|
@ -196,7 +215,7 @@ impl CommandSocket {
|
||||||
Some(handler) => {
|
Some(handler) => {
|
||||||
let args = param.get("args"); //.unwrap_or(&Value::Null);
|
let args = param.get("args"); //.unwrap_or(&Value::Null);
|
||||||
(handler)(args)
|
(handler)(args)
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
@ -206,15 +225,10 @@ impl CommandSocket {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register a new command with a callback.
|
/// Register a new command with a callback.
|
||||||
pub fn register_command<F>(
|
pub fn register_command<F>(&mut self, command: String, handler: F) -> Result<(), Error>
|
||||||
&mut self,
|
|
||||||
command: String,
|
|
||||||
handler: F,
|
|
||||||
) -> Result<(), Error>
|
|
||||||
where
|
where
|
||||||
F: Fn(Option<&Value>) -> Result<Value, Error> + Send + Sync + 'static,
|
F: Fn(Option<&Value>) -> Result<Value, Error> + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
|
|
||||||
if self.commands.contains_key(&command) {
|
if self.commands.contains_key(&command) {
|
||||||
bail!("command '{}' already exists!", command);
|
bail!("command '{}' already exists!", command);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::os::raw::{c_char, c_uchar, c_int};
|
use std::os::raw::{c_char, c_int, c_uchar};
|
||||||
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
|
|
||||||
use std::os::unix::ffi::OsStrExt;
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
|
||||||
use std::panic::UnwindSafe;
|
use std::panic::UnwindSafe;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
@ -13,8 +13,8 @@ use anyhow::{bail, format_err, Error};
|
||||||
use futures::future::{self, Either};
|
use futures::future::{self, Either};
|
||||||
use nix::unistd::{fork, ForkResult};
|
use nix::unistd::{fork, ForkResult};
|
||||||
|
|
||||||
use proxmox_sys::fd::{fd_change_cloexec, Fd};
|
|
||||||
use proxmox_io::{ReadExt, WriteExt};
|
use proxmox_io::{ReadExt, WriteExt};
|
||||||
|
use proxmox_sys::fd::{fd_change_cloexec, Fd};
|
||||||
|
|
||||||
// Unfortunately FnBox is nightly-only and Box<FnOnce> is unusable, so just use Box<Fn>...
|
// Unfortunately FnBox is nightly-only and Box<FnOnce> is unusable, so just use Box<Fn>...
|
||||||
type BoxedStoreFunc = Box<dyn FnMut() -> Result<String, Error> + UnwindSafe + Send>;
|
type BoxedStoreFunc = Box<dyn FnMut() -> Result<String, Error> + UnwindSafe + Send>;
|
||||||
|
@ -102,9 +102,8 @@ impl Reloader {
|
||||||
// At this point we call pre-exec helpers. We must be certain that if they fail for
|
// At this point we call pre-exec helpers. We must be certain that if they fail for
|
||||||
// whatever reason we can still call `_exit()`, so use catch_unwind.
|
// whatever reason we can still call `_exit()`, so use catch_unwind.
|
||||||
match std::panic::catch_unwind(move || {
|
match std::panic::catch_unwind(move || {
|
||||||
let mut pnew = unsafe {
|
let mut pnew =
|
||||||
std::fs::File::from_raw_fd(pnew.into_raw_fd())
|
unsafe { std::fs::File::from_raw_fd(pnew.into_raw_fd()) };
|
||||||
};
|
|
||||||
let pid = nix::unistd::Pid::this();
|
let pid = nix::unistd::Pid::this();
|
||||||
if let Err(e) = unsafe { pnew.write_host_value(pid.as_raw()) } {
|
if let Err(e) = unsafe { pnew.write_host_value(pid.as_raw()) } {
|
||||||
log::error!("failed to send new server PID to parent: {}", e);
|
log::error!("failed to send new server PID to parent: {}", e);
|
||||||
|
@ -125,16 +124,19 @@ impl Reloader {
|
||||||
std::mem::drop(pnew);
|
std::mem::drop(pnew);
|
||||||
|
|
||||||
// Try to reopen STDOUT/STDERR journald streams to get correct PID in logs
|
// Try to reopen STDOUT/STDERR journald streams to get correct PID in logs
|
||||||
let ident = CString::new(self.self_exe.file_name().unwrap().as_bytes()).unwrap();
|
let ident = CString::new(self.self_exe.file_name().unwrap().as_bytes())
|
||||||
|
.unwrap();
|
||||||
let ident = ident.as_bytes();
|
let ident = ident.as_bytes();
|
||||||
let fd = unsafe { sd_journal_stream_fd(ident.as_ptr(), libc::LOG_INFO, 1) };
|
let fd =
|
||||||
|
unsafe { sd_journal_stream_fd(ident.as_ptr(), libc::LOG_INFO, 1) };
|
||||||
if fd >= 0 && fd != 1 {
|
if fd >= 0 && fd != 1 {
|
||||||
let fd = proxmox_sys::fd::Fd(fd); // add drop handler
|
let fd = proxmox_sys::fd::Fd(fd); // add drop handler
|
||||||
nix::unistd::dup2(fd.as_raw_fd(), 1)?;
|
nix::unistd::dup2(fd.as_raw_fd(), 1)?;
|
||||||
} else {
|
} else {
|
||||||
log::error!("failed to update STDOUT journal redirection ({})", fd);
|
log::error!("failed to update STDOUT journal redirection ({})", fd);
|
||||||
}
|
}
|
||||||
let fd = unsafe { sd_journal_stream_fd(ident.as_ptr(), libc::LOG_ERR, 1) };
|
let fd =
|
||||||
|
unsafe { sd_journal_stream_fd(ident.as_ptr(), libc::LOG_ERR, 1) };
|
||||||
if fd >= 0 && fd != 2 {
|
if fd >= 0 && fd != 2 {
|
||||||
let fd = proxmox_sys::fd::Fd(fd); // add drop handler
|
let fd = proxmox_sys::fd::Fd(fd); // add drop handler
|
||||||
nix::unistd::dup2(fd.as_raw_fd(), 2)?;
|
nix::unistd::dup2(fd.as_raw_fd(), 2)?;
|
||||||
|
@ -143,8 +145,7 @@ impl Reloader {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.do_reexec(new_args)
|
self.do_reexec(new_args)
|
||||||
})
|
}) {
|
||||||
{
|
|
||||||
Ok(Ok(())) => log::error!("do_reexec returned!"),
|
Ok(Ok(())) => log::error!("do_reexec returned!"),
|
||||||
Ok(Err(err)) => log::error!("do_reexec failed: {}", err),
|
Ok(Err(err)) => log::error!("do_reexec failed: {}", err),
|
||||||
Err(_) => log::error!("panic in re-exec"),
|
Err(_) => log::error!("panic in re-exec"),
|
||||||
|
@ -157,20 +158,22 @@ impl Reloader {
|
||||||
Err(e) => log::error!("fork() failed, restart delayed: {}", e),
|
Err(e) => log::error!("fork() failed, restart delayed: {}", e),
|
||||||
}
|
}
|
||||||
// No matter how we managed to get here, this is the time where we bail out quickly:
|
// No matter how we managed to get here, this is the time where we bail out quickly:
|
||||||
unsafe {
|
unsafe { libc::_exit(-1) }
|
||||||
libc::_exit(-1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(ForkResult::Parent { child }) => {
|
Ok(ForkResult::Parent { child }) => {
|
||||||
log::debug!("forked off a new server (first pid: {}), waiting for 2nd pid", child);
|
log::debug!(
|
||||||
|
"forked off a new server (first pid: {}), waiting for 2nd pid",
|
||||||
|
child
|
||||||
|
);
|
||||||
std::mem::drop(pnew);
|
std::mem::drop(pnew);
|
||||||
let mut pold = unsafe {
|
let mut pold = unsafe { std::fs::File::from_raw_fd(pold.into_raw_fd()) };
|
||||||
std::fs::File::from_raw_fd(pold.into_raw_fd())
|
|
||||||
};
|
|
||||||
let child = nix::unistd::Pid::from_raw(match unsafe { pold.read_le_value() } {
|
let child = nix::unistd::Pid::from_raw(match unsafe { pold.read_le_value() } {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("failed to receive pid of double-forked child process: {}", e);
|
log::error!(
|
||||||
|
"failed to receive pid of double-forked child process: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
// systemd will complain but won't kill the service...
|
// systemd will complain but won't kill the service...
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
@ -215,9 +218,10 @@ impl Reloadable for tokio::net::TcpListener {
|
||||||
// FIXME: We could become "independent" of the TcpListener and its reference to the file
|
// FIXME: We could become "independent" of the TcpListener and its reference to the file
|
||||||
// descriptor by `dup()`ing it (and check if the listener still exists via kcmp()?)
|
// descriptor by `dup()`ing it (and check if the listener still exists via kcmp()?)
|
||||||
fn get_store_func(&self) -> Result<BoxedStoreFunc, Error> {
|
fn get_store_func(&self) -> Result<BoxedStoreFunc, Error> {
|
||||||
let mut fd_opt = Some(Fd(
|
let mut fd_opt = Some(Fd(nix::fcntl::fcntl(
|
||||||
nix::fcntl::fcntl(self.as_raw_fd(), nix::fcntl::FcntlArg::F_DUPFD_CLOEXEC(0))?
|
self.as_raw_fd(),
|
||||||
));
|
nix::fcntl::FcntlArg::F_DUPFD_CLOEXEC(0),
|
||||||
|
)?));
|
||||||
Ok(Box::new(move || {
|
Ok(Box::new(move || {
|
||||||
let fd = fd_opt.take().unwrap();
|
let fd = fd_opt.take().unwrap();
|
||||||
fd_change_cloexec(fd.as_raw_fd(), false)?;
|
fd_change_cloexec(fd.as_raw_fd(), false)?;
|
||||||
|
@ -226,13 +230,13 @@ impl Reloadable for tokio::net::TcpListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn restore(var: &str) -> Result<Self, Error> {
|
fn restore(var: &str) -> Result<Self, Error> {
|
||||||
let fd = var.parse::<u32>()
|
let fd = var
|
||||||
.map_err(|e| format_err!("invalid file descriptor: {}", e))?
|
.parse::<u32>()
|
||||||
as RawFd;
|
.map_err(|e| format_err!("invalid file descriptor: {}", e))? as RawFd;
|
||||||
fd_change_cloexec(fd, true)?;
|
fd_change_cloexec(fd, true)?;
|
||||||
Ok(Self::from_std(
|
Ok(Self::from_std(unsafe {
|
||||||
unsafe { std::net::TcpListener::from_raw_fd(fd) },
|
std::net::TcpListener::from_raw_fd(fd)
|
||||||
)?)
|
})?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,10 +257,11 @@ where
|
||||||
{
|
{
|
||||||
let mut reloader = Reloader::new()?;
|
let mut reloader = Reloader::new()?;
|
||||||
|
|
||||||
let listener: tokio::net::TcpListener = reloader.restore(
|
let listener: tokio::net::TcpListener = reloader
|
||||||
"PROXMOX_BACKUP_LISTEN_FD",
|
.restore("PROXMOX_BACKUP_LISTEN_FD", move || async move {
|
||||||
move || async move { Ok(tokio::net::TcpListener::bind(&address).await?) },
|
Ok(tokio::net::TcpListener::bind(&address).await?)
|
||||||
).await?;
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
let service = create_service(listener)?;
|
let service = create_service(listener)?;
|
||||||
|
|
||||||
|
@ -308,7 +313,11 @@ where
|
||||||
|
|
||||||
#[link(name = "systemd")]
|
#[link(name = "systemd")]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
fn sd_journal_stream_fd(identifier: *const c_uchar, priority: c_int, level_prefix: c_int) -> c_int;
|
fn sd_journal_stream_fd(
|
||||||
|
identifier: *const c_uchar,
|
||||||
|
priority: c_int,
|
||||||
|
level_prefix: c_int,
|
||||||
|
) -> c_int;
|
||||||
fn sd_notify(unset_environment: c_int, state: *const c_char) -> c_int;
|
fn sd_notify(unset_environment: c_int, state: *const c_char) -> c_int;
|
||||||
fn sd_notify_barrier(unset_environment: c_int, timeout: u64) -> c_int;
|
fn sd_notify_barrier(unset_environment: c_int, timeout: u64) -> c_int;
|
||||||
}
|
}
|
||||||
|
@ -328,7 +337,7 @@ pub fn systemd_notify(state: SystemdNotify) -> Result<(), Error> {
|
||||||
SystemdNotify::Ready => {
|
SystemdNotify::Ready => {
|
||||||
log::info!("service is ready");
|
log::info!("service is ready");
|
||||||
CString::new("READY=1")
|
CString::new("READY=1")
|
||||||
},
|
}
|
||||||
SystemdNotify::Reloading => CString::new("RELOADING=1"),
|
SystemdNotify::Reloading => CString::new("RELOADING=1"),
|
||||||
SystemdNotify::Stopping => CString::new("STOPPING=1"),
|
SystemdNotify::Stopping => CString::new("STOPPING=1"),
|
||||||
SystemdNotify::Status(msg) => CString::new(format!("STATUS={}", msg)),
|
SystemdNotify::Status(msg) => CString::new(format!("STATUS={}", msg)),
|
||||||
|
@ -336,7 +345,10 @@ pub fn systemd_notify(state: SystemdNotify) -> Result<(), Error> {
|
||||||
}?;
|
}?;
|
||||||
let rc = unsafe { sd_notify(0, message.as_ptr()) };
|
let rc = unsafe { sd_notify(0, message.as_ptr()) };
|
||||||
if rc < 0 {
|
if rc < 0 {
|
||||||
bail!("systemd_notify failed: {}", std::io::Error::from_raw_os_error(-rc));
|
bail!(
|
||||||
|
"systemd_notify failed: {}",
|
||||||
|
std::io::Error::from_raw_os_error(-rc)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -346,7 +358,10 @@ pub fn systemd_notify(state: SystemdNotify) -> Result<(), Error> {
|
||||||
pub fn systemd_notify_barrier(timeout: u64) -> Result<(), Error> {
|
pub fn systemd_notify_barrier(timeout: u64) -> Result<(), Error> {
|
||||||
let rc = unsafe { sd_notify_barrier(0, timeout) };
|
let rc = unsafe { sd_notify_barrier(0, timeout) };
|
||||||
if rc < 0 {
|
if rc < 0 {
|
||||||
bail!("systemd_notify_barrier failed: {}", std::io::Error::from_raw_os_error(-rc));
|
bail!(
|
||||||
|
"systemd_notify_barrier failed: {}",
|
||||||
|
std::io::Error::from_raw_os_error(-rc)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use std::sync::Arc;
|
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
|
@ -42,13 +42,19 @@ impl RestEnvironment {
|
||||||
pub fn log_failed_auth(&self, failed_auth_id: Option<String>, msg: &str) {
|
pub fn log_failed_auth(&self, failed_auth_id: Option<String>, msg: &str) {
|
||||||
let msg = match (self.client_ip, failed_auth_id) {
|
let msg = match (self.client_ip, failed_auth_id) {
|
||||||
(Some(peer), Some(user)) => {
|
(Some(peer), Some(user)) => {
|
||||||
format!("authentication failure; rhost={} user={} msg={}", peer, user, msg)
|
format!(
|
||||||
|
"authentication failure; rhost={} user={} msg={}",
|
||||||
|
peer, user, msg
|
||||||
|
)
|
||||||
}
|
}
|
||||||
(Some(peer), None) => {
|
(Some(peer), None) => {
|
||||||
format!("authentication failure; rhost={} msg={}", peer, msg)
|
format!("authentication failure; rhost={} msg={}", peer, msg)
|
||||||
}
|
}
|
||||||
(None, Some(user)) => {
|
(None, Some(user)) => {
|
||||||
format!("authentication failure; rhost=unknown user={} msg={}", user, msg)
|
format!(
|
||||||
|
"authentication failure; rhost=unknown user={} msg={}",
|
||||||
|
user, msg
|
||||||
|
)
|
||||||
}
|
}
|
||||||
(None, None) => {
|
(None, None) => {
|
||||||
format!("authentication failure; rhost=unknown msg={}", msg)
|
format!("authentication failure; rhost=unknown msg={}", msg)
|
||||||
|
@ -59,11 +65,9 @@ impl RestEnvironment {
|
||||||
auth_logger.lock().unwrap().log(&msg);
|
auth_logger.lock().unwrap().log(&msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RpcEnvironment for RestEnvironment {
|
impl RpcEnvironment for RestEnvironment {
|
||||||
|
|
||||||
fn result_attrib_mut(&mut self) -> &mut Value {
|
fn result_attrib_mut(&mut self) -> &mut Value {
|
||||||
&mut self.result_attributes
|
&mut self.result_attributes
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::io::Write;
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use nix::fcntl::OFlag;
|
use nix::fcntl::OFlag;
|
||||||
|
|
||||||
use proxmox_sys::fs::{CreateOptions, atomic_open_or_create_file};
|
use proxmox_sys::fs::{atomic_open_or_create_file, CreateOptions};
|
||||||
|
|
||||||
/// Options to control the behavior of a [FileLogger] instance
|
/// Options to control the behavior of a [FileLogger] instance
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -23,7 +23,6 @@ pub struct FileLogOptions {
|
||||||
pub prefix_time: bool,
|
pub prefix_time: bool,
|
||||||
/// File owner/group and mode
|
/// File owner/group and mode
|
||||||
pub file_opts: CreateOptions,
|
pub file_opts: CreateOptions,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Log messages with optional automatically added timestamps into files
|
/// Log messages with optional automatically added timestamps into files
|
||||||
|
@ -66,7 +65,11 @@ impl FileLogger {
|
||||||
|
|
||||||
let file_name: std::path::PathBuf = file_name.as_ref().to_path_buf();
|
let file_name: std::path::PathBuf = file_name.as_ref().to_path_buf();
|
||||||
|
|
||||||
Ok(Self { file, file_name, options })
|
Ok(Self {
|
||||||
|
file,
|
||||||
|
file_name,
|
||||||
|
options,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reopen(&mut self) -> Result<&Self, Error> {
|
pub fn reopen(&mut self) -> Result<&Self, Error> {
|
||||||
|
@ -79,7 +82,6 @@ impl FileLogger {
|
||||||
file_name: P,
|
file_name: P,
|
||||||
options: &FileLogOptions,
|
options: &FileLogOptions,
|
||||||
) -> Result<std::fs::File, Error> {
|
) -> Result<std::fs::File, Error> {
|
||||||
|
|
||||||
let mut flags = OFlag::O_CLOEXEC;
|
let mut flags = OFlag::O_CLOEXEC;
|
||||||
|
|
||||||
if options.read {
|
if options.read {
|
||||||
|
@ -95,7 +97,8 @@ impl FileLogger {
|
||||||
flags |= OFlag::O_EXCL;
|
flags |= OFlag::O_EXCL;
|
||||||
}
|
}
|
||||||
|
|
||||||
let file = atomic_open_or_create_file(&file_name, flags, &[], options.file_opts.clone(), false)?;
|
let file =
|
||||||
|
atomic_open_or_create_file(&file_name, flags, &[], options.file_opts.clone(), false)?;
|
||||||
|
|
||||||
Ok(file)
|
Ok(file)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
//! Helpers to format response data
|
//! Helpers to format response data
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use anyhow::{Error};
|
use anyhow::Error;
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
use hyper::{Body, Response, StatusCode};
|
|
||||||
use hyper::header;
|
use hyper::header;
|
||||||
|
use hyper::{Body, Response, StatusCode};
|
||||||
|
|
||||||
use proxmox_router::{HttpError, RpcEnvironment};
|
use proxmox_router::{HttpError, RpcEnvironment};
|
||||||
use proxmox_schema::ParameterError;
|
use proxmox_schema::ParameterError;
|
||||||
|
@ -22,7 +22,11 @@ pub trait OutputFormatter: Send + Sync {
|
||||||
fn format_error(&self, err: Error) -> Response<Body>;
|
fn format_error(&self, err: Error) -> Response<Body>;
|
||||||
|
|
||||||
/// Transform a [Result] into a http response
|
/// Transform a [Result] into a http response
|
||||||
fn format_result(&self, result: Result<Value, Error>, rpcenv: &dyn RpcEnvironment) -> Response<Body> {
|
fn format_result(
|
||||||
|
&self,
|
||||||
|
result: Result<Value, Error>,
|
||||||
|
rpcenv: &dyn RpcEnvironment,
|
||||||
|
) -> Response<Body> {
|
||||||
match result {
|
match result {
|
||||||
Ok(data) => self.format_data(data, rpcenv),
|
Ok(data) => self.format_data(data, rpcenv),
|
||||||
Err(err) => self.format_error(err),
|
Err(err) => self.format_error(err),
|
||||||
|
@ -33,7 +37,6 @@ pub trait OutputFormatter: Send + Sync {
|
||||||
static JSON_CONTENT_TYPE: &str = "application/json;charset=UTF-8";
|
static JSON_CONTENT_TYPE: &str = "application/json;charset=UTF-8";
|
||||||
|
|
||||||
fn json_data_response(data: Value) -> Response<Body> {
|
fn json_data_response(data: Value) -> Response<Body> {
|
||||||
|
|
||||||
let json_str = data.to_string();
|
let json_str = data.to_string();
|
||||||
|
|
||||||
let raw = json_str.into_bytes();
|
let raw = json_str.into_bytes();
|
||||||
|
@ -41,13 +44,13 @@ fn json_data_response(data: Value) -> Response<Body> {
|
||||||
let mut response = Response::new(raw.into());
|
let mut response = Response::new(raw.into());
|
||||||
response.headers_mut().insert(
|
response.headers_mut().insert(
|
||||||
header::CONTENT_TYPE,
|
header::CONTENT_TYPE,
|
||||||
header::HeaderValue::from_static(JSON_CONTENT_TYPE));
|
header::HeaderValue::from_static(JSON_CONTENT_TYPE),
|
||||||
|
);
|
||||||
|
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_result_attributes(result: &mut Value, rpcenv: &dyn RpcEnvironment)
|
fn add_result_attributes(result: &mut Value, rpcenv: &dyn RpcEnvironment) {
|
||||||
{
|
|
||||||
let attributes = match rpcenv.result_attrib().as_object() {
|
let attributes = match rpcenv.result_attrib().as_object() {
|
||||||
Some(attr) => attr,
|
Some(attr) => attr,
|
||||||
None => return,
|
None => return,
|
||||||
|
@ -58,7 +61,6 @@ fn add_result_attributes(result: &mut Value, rpcenv: &dyn RpcEnvironment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct JsonFormatter();
|
struct JsonFormatter();
|
||||||
|
|
||||||
/// Format data as ``application/json``
|
/// Format data as ``application/json``
|
||||||
|
@ -74,12 +76,8 @@ struct JsonFormatter();
|
||||||
pub static JSON_FORMATTER: &'static dyn OutputFormatter = &JsonFormatter();
|
pub static JSON_FORMATTER: &'static dyn OutputFormatter = &JsonFormatter();
|
||||||
|
|
||||||
impl OutputFormatter for JsonFormatter {
|
impl OutputFormatter for JsonFormatter {
|
||||||
|
|
||||||
fn format_data(&self, data: Value, rpcenv: &dyn RpcEnvironment) -> Response<Body> {
|
fn format_data(&self, data: Value, rpcenv: &dyn RpcEnvironment) -> Response<Body> {
|
||||||
|
let mut result = json!({ "data": data });
|
||||||
let mut result = json!({
|
|
||||||
"data": data
|
|
||||||
});
|
|
||||||
|
|
||||||
add_result_attributes(&mut result, rpcenv);
|
add_result_attributes(&mut result, rpcenv);
|
||||||
|
|
||||||
|
@ -87,7 +85,6 @@ impl OutputFormatter for JsonFormatter {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_error(&self, err: Error) -> Response<Body> {
|
fn format_error(&self, err: Error) -> Response<Body> {
|
||||||
|
|
||||||
let mut response = if let Some(apierr) = err.downcast_ref::<HttpError>() {
|
let mut response = if let Some(apierr) = err.downcast_ref::<HttpError>() {
|
||||||
let mut resp = Response::new(Body::from(apierr.message.clone()));
|
let mut resp = Response::new(Body::from(apierr.message.clone()));
|
||||||
*resp.status_mut() = apierr.code;
|
*resp.status_mut() = apierr.code;
|
||||||
|
@ -100,9 +97,12 @@ impl OutputFormatter for JsonFormatter {
|
||||||
|
|
||||||
response.headers_mut().insert(
|
response.headers_mut().insert(
|
||||||
header::CONTENT_TYPE,
|
header::CONTENT_TYPE,
|
||||||
header::HeaderValue::from_static(JSON_CONTENT_TYPE));
|
header::HeaderValue::from_static(JSON_CONTENT_TYPE),
|
||||||
|
);
|
||||||
|
|
||||||
response.extensions_mut().insert(ErrorMessageExtension(err.to_string()));
|
response
|
||||||
|
.extensions_mut()
|
||||||
|
.insert(ErrorMessageExtension(err.to_string()));
|
||||||
|
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
@ -129,9 +129,7 @@ pub static EXTJS_FORMATTER: &'static dyn OutputFormatter = &ExtJsFormatter();
|
||||||
struct ExtJsFormatter();
|
struct ExtJsFormatter();
|
||||||
|
|
||||||
impl OutputFormatter for ExtJsFormatter {
|
impl OutputFormatter for ExtJsFormatter {
|
||||||
|
|
||||||
fn format_data(&self, data: Value, rpcenv: &dyn RpcEnvironment) -> Response<Body> {
|
fn format_data(&self, data: Value, rpcenv: &dyn RpcEnvironment) -> Response<Body> {
|
||||||
|
|
||||||
let mut result = json!({
|
let mut result = json!({
|
||||||
"data": data,
|
"data": data,
|
||||||
"success": true
|
"success": true
|
||||||
|
@ -143,7 +141,6 @@ impl OutputFormatter for ExtJsFormatter {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_error(&self, err: Error) -> Response<Body> {
|
fn format_error(&self, err: Error) -> Response<Body> {
|
||||||
|
|
||||||
let message: String;
|
let message: String;
|
||||||
let mut errors = HashMap::new();
|
let mut errors = HashMap::new();
|
||||||
|
|
||||||
|
@ -165,7 +162,9 @@ impl OutputFormatter for ExtJsFormatter {
|
||||||
|
|
||||||
let mut response = json_data_response(result);
|
let mut response = json_data_response(result);
|
||||||
|
|
||||||
response.extensions_mut().insert(ErrorMessageExtension(message));
|
response
|
||||||
|
.extensions_mut()
|
||||||
|
.insert(ErrorMessageExtension(message));
|
||||||
|
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use anyhow::{Error};
|
use anyhow::Error;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
@ -8,11 +8,11 @@ use std::task::{Context, Poll};
|
||||||
use futures::*;
|
use futures::*;
|
||||||
use hyper::{Body, Request, Response, StatusCode};
|
use hyper::{Body, Request, Response, StatusCode};
|
||||||
|
|
||||||
use proxmox_router::{ApiResponseFuture, HttpError, Router, RpcEnvironment};
|
|
||||||
use proxmox_router::http_err;
|
use proxmox_router::http_err;
|
||||||
|
use proxmox_router::{ApiResponseFuture, HttpError, Router, RpcEnvironment};
|
||||||
|
|
||||||
use crate::{normalize_uri_path, WorkerTask};
|
|
||||||
use crate::formatter::*;
|
use crate::formatter::*;
|
||||||
|
use crate::{normalize_uri_path, WorkerTask};
|
||||||
|
|
||||||
/// Hyper Service implementation to handle stateful H2 connections.
|
/// Hyper Service implementation to handle stateful H2 connections.
|
||||||
///
|
///
|
||||||
|
@ -27,17 +27,22 @@ pub struct H2Service<E> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: RpcEnvironment + Clone> H2Service<E> {
|
impl<E: RpcEnvironment + Clone> H2Service<E> {
|
||||||
|
|
||||||
pub fn new(rpcenv: E, worker: Arc<WorkerTask>, router: &'static Router, debug: bool) -> Self {
|
pub fn new(rpcenv: E, worker: Arc<WorkerTask>, router: &'static Router, debug: bool) -> Self {
|
||||||
Self { rpcenv, worker, router, debug }
|
Self {
|
||||||
|
rpcenv,
|
||||||
|
worker,
|
||||||
|
router,
|
||||||
|
debug,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn debug<S: AsRef<str>>(&self, msg: S) {
|
pub fn debug<S: AsRef<str>>(&self, msg: S) {
|
||||||
if self.debug { self.worker.log_message(msg); }
|
if self.debug {
|
||||||
|
self.worker.log_message(msg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_request(&self, req: Request<Body>) -> ApiResponseFuture {
|
fn handle_request(&self, req: Request<Body>) -> ApiResponseFuture {
|
||||||
|
|
||||||
let (parts, body) = req.into_parts();
|
let (parts, body) = req.into_parts();
|
||||||
|
|
||||||
let method = parts.method.clone();
|
let method = parts.method.clone();
|
||||||
|
@ -58,15 +63,24 @@ impl <E: RpcEnvironment + Clone> H2Service<E> {
|
||||||
let err = http_err!(NOT_FOUND, "Path '{}' not found.", path);
|
let err = http_err!(NOT_FOUND, "Path '{}' not found.", path);
|
||||||
future::ok(formatter.format_error(err)).boxed()
|
future::ok(formatter.format_error(err)).boxed()
|
||||||
}
|
}
|
||||||
Some(api_method) => {
|
Some(api_method) => crate::rest::handle_api_request(
|
||||||
crate::rest::handle_api_request(
|
self.rpcenv.clone(),
|
||||||
self.rpcenv.clone(), api_method, formatter, parts, body, uri_param).boxed()
|
api_method,
|
||||||
}
|
formatter,
|
||||||
|
parts,
|
||||||
|
body,
|
||||||
|
uri_param,
|
||||||
|
)
|
||||||
|
.boxed(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn log_response(worker: Arc<WorkerTask>, method: hyper::Method, path: &str, resp: &Response<Body>) {
|
fn log_response(
|
||||||
|
worker: Arc<WorkerTask>,
|
||||||
|
method: hyper::Method,
|
||||||
|
path: &str,
|
||||||
|
resp: &Response<Body>,
|
||||||
|
) {
|
||||||
let status = resp.status();
|
let status = resp.status();
|
||||||
|
|
||||||
if !status.is_success() {
|
if !status.is_success() {
|
||||||
|
@ -113,13 +127,15 @@ impl <E: RpcEnvironment + Clone> tower_service::Service<Request<Body>> for H2Ser
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if let Some(apierr) = err.downcast_ref::<HttpError>() {
|
if let Some(apierr) = err.downcast_ref::<HttpError>() {
|
||||||
let mut resp = Response::new(Body::from(apierr.message.clone()));
|
let mut resp = Response::new(Body::from(apierr.message.clone()));
|
||||||
resp.extensions_mut().insert(ErrorMessageExtension(apierr.message.clone()));
|
resp.extensions_mut()
|
||||||
|
.insert(ErrorMessageExtension(apierr.message.clone()));
|
||||||
*resp.status_mut() = apierr.code;
|
*resp.status_mut() = apierr.code;
|
||||||
Self::log_response(worker, method, &path, &resp);
|
Self::log_response(worker, method, &path, &resp);
|
||||||
Ok(resp)
|
Ok(resp)
|
||||||
} else {
|
} else {
|
||||||
let mut resp = Response::new(Body::from(err.to_string()));
|
let mut resp = Response::new(Body::from(err.to_string()));
|
||||||
resp.extensions_mut().insert(ErrorMessageExtension(err.to_string()));
|
resp.extensions_mut()
|
||||||
|
.insert(ErrorMessageExtension(err.to_string()));
|
||||||
*resp.status_mut() = StatusCode::BAD_REQUEST;
|
*resp.status_mut() = StatusCode::BAD_REQUEST;
|
||||||
Self::log_response(worker, method, &path, &resp);
|
Self::log_response(worker, method, &path, &resp);
|
||||||
Ok(resp)
|
Ok(resp)
|
||||||
|
|
|
@ -15,20 +15,20 @@
|
||||||
//! - worker task management
|
//! - worker task management
|
||||||
//! * generic interface to authenticate user
|
//! * generic interface to authenticate user
|
||||||
|
|
||||||
use std::sync::atomic::{Ordering, AtomicBool};
|
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
use anyhow::{bail, format_err, Error};
|
use anyhow::{bail, format_err, Error};
|
||||||
use nix::unistd::Pid;
|
|
||||||
use hyper::{Body, Response, Method};
|
|
||||||
use http::request::Parts;
|
use http::request::Parts;
|
||||||
use http::HeaderMap;
|
use http::HeaderMap;
|
||||||
|
use hyper::{Body, Method, Response};
|
||||||
|
use nix::unistd::Pid;
|
||||||
|
|
||||||
use proxmox_sys::fd::Fd;
|
|
||||||
use proxmox_sys::linux::procfs::PidStat;
|
|
||||||
use proxmox_sys::fs::CreateOptions;
|
|
||||||
use proxmox_router::UserInformation;
|
use proxmox_router::UserInformation;
|
||||||
|
use proxmox_sys::fd::Fd;
|
||||||
|
use proxmox_sys::fs::CreateOptions;
|
||||||
|
use proxmox_sys::linux::procfs::PidStat;
|
||||||
|
|
||||||
mod compression;
|
mod compression;
|
||||||
pub use compression::*;
|
pub use compression::*;
|
||||||
|
@ -47,7 +47,7 @@ mod command_socket;
|
||||||
pub use command_socket::*;
|
pub use command_socket::*;
|
||||||
|
|
||||||
mod file_logger;
|
mod file_logger;
|
||||||
pub use file_logger::{FileLogger, FileLogOptions};
|
pub use file_logger::{FileLogOptions, FileLogger};
|
||||||
|
|
||||||
mod api_config;
|
mod api_config;
|
||||||
pub use api_config::ApiConfig;
|
pub use api_config::ApiConfig;
|
||||||
|
@ -75,7 +75,6 @@ impl From<Error> for AuthError {
|
||||||
|
|
||||||
/// User Authentication and index/root page generation methods
|
/// User Authentication and index/root page generation methods
|
||||||
pub trait ServerAdapter: Send + Sync {
|
pub trait ServerAdapter: Send + Sync {
|
||||||
|
|
||||||
/// Returns the index/root page
|
/// Returns the index/root page
|
||||||
fn get_index(
|
fn get_index(
|
||||||
&self,
|
&self,
|
||||||
|
@ -91,8 +90,13 @@ pub trait ServerAdapter: Send + Sync {
|
||||||
&'a self,
|
&'a self,
|
||||||
headers: &'a HeaderMap,
|
headers: &'a HeaderMap,
|
||||||
method: &'a Method,
|
method: &'a Method,
|
||||||
) -> Pin<Box<dyn Future<Output = Result<(String, Box<dyn UserInformation + Sync + Send>), AuthError>> + Send + 'a>>;
|
) -> Pin<
|
||||||
|
Box<
|
||||||
|
dyn Future<Output = Result<(String, Box<dyn UserInformation + Sync + Send>), AuthError>>
|
||||||
|
+ Send
|
||||||
|
+ 'a,
|
||||||
|
>,
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
|
@ -124,7 +128,8 @@ pub fn write_pid(pid_fn: &str) -> Result<(), Error> {
|
||||||
pub fn read_pid(pid_fn: &str) -> Result<i32, Error> {
|
pub fn read_pid(pid_fn: &str) -> Result<i32, Error> {
|
||||||
let pid = proxmox_sys::fs::file_get_contents(pid_fn)?;
|
let pid = proxmox_sys::fs::file_get_contents(pid_fn)?;
|
||||||
let pid = std::str::from_utf8(&pid)?.trim();
|
let pid = std::str::from_utf8(&pid)?.trim();
|
||||||
pid.parse().map_err(|err| format_err!("could not parse pid - {}", err))
|
pid.parse()
|
||||||
|
.map_err(|err| format_err!("could not parse pid - {}", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the control socket path for a specific process ID.
|
/// Returns the control socket path for a specific process ID.
|
||||||
|
@ -178,7 +183,6 @@ pub fn socketpair() -> Result<(Fd, Fd), Error> {
|
||||||
Ok((Fd(pa), Fd(pb)))
|
Ok((Fd(pa), Fd(pb)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Extract a specific cookie from cookie header.
|
/// Extract a specific cookie from cookie header.
|
||||||
/// We assume cookie_name is already url encoded.
|
/// We assume cookie_name is already url encoded.
|
||||||
pub fn extract_cookie(cookie: &str, cookie_name: &str) -> Option<String> {
|
pub fn extract_cookie(cookie: &str, cookie_name: &str) -> Option<String> {
|
||||||
|
|
|
@ -18,24 +18,24 @@ use regex::Regex;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use tokio::fs::File;
|
use tokio::fs::File;
|
||||||
use tokio::time::Instant;
|
use tokio::time::Instant;
|
||||||
use url::form_urlencoded;
|
|
||||||
use tower_service::Service;
|
use tower_service::Service;
|
||||||
|
use url::form_urlencoded;
|
||||||
|
|
||||||
|
use proxmox_router::http_err;
|
||||||
use proxmox_router::{
|
use proxmox_router::{
|
||||||
check_api_permission, ApiHandler, ApiMethod, HttpError, Permission, RpcEnvironment,
|
check_api_permission, ApiHandler, ApiMethod, HttpError, Permission, RpcEnvironment,
|
||||||
RpcEnvironmentType, UserInformation,
|
RpcEnvironmentType, UserInformation,
|
||||||
};
|
};
|
||||||
use proxmox_router::http_err;
|
|
||||||
use proxmox_schema::{ObjectSchemaType, ParameterSchema};
|
use proxmox_schema::{ObjectSchemaType, ParameterSchema};
|
||||||
|
|
||||||
use proxmox_http::client::RateLimitedStream;
|
use proxmox_http::client::RateLimitedStream;
|
||||||
|
|
||||||
use proxmox_compression::{DeflateEncoder, Level};
|
|
||||||
use proxmox_async::stream::AsyncReaderStream;
|
use proxmox_async::stream::AsyncReaderStream;
|
||||||
|
use proxmox_compression::{DeflateEncoder, Level};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ApiConfig, FileLogger, AuthError, RestEnvironment, CompressionMethod,
|
formatter::*, normalize_uri_path, ApiConfig, AuthError, CompressionMethod, FileLogger,
|
||||||
normalize_uri_path, formatter::*,
|
RestEnvironment,
|
||||||
};
|
};
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
@ -47,9 +47,15 @@ struct AuthStringExtension(String);
|
||||||
struct EmptyUserInformation {}
|
struct EmptyUserInformation {}
|
||||||
|
|
||||||
impl UserInformation for EmptyUserInformation {
|
impl UserInformation for EmptyUserInformation {
|
||||||
fn is_superuser(&self, _userid: &str) -> bool { false }
|
fn is_superuser(&self, _userid: &str) -> bool {
|
||||||
fn is_group_member(&self, _userid: &str, _group: &str) -> bool { false }
|
false
|
||||||
fn lookup_privs(&self, _userid: &str, _path: &[&str]) -> u64 { 0 }
|
}
|
||||||
|
fn is_group_member(&self, _userid: &str, _group: &str) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
fn lookup_privs(&self, _userid: &str, _path: &[&str]) -> u64 {
|
||||||
|
0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// REST server implementation (configured with [ApiConfig])
|
/// REST server implementation (configured with [ApiConfig])
|
||||||
|
@ -98,9 +104,7 @@ impl Service<&Pin<Box<tokio_openssl::SslStream<RateLimitedStream<tokio::net::Tcp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Service<&Pin<Box<tokio_openssl::SslStream<tokio::net::TcpStream>>>>
|
impl Service<&Pin<Box<tokio_openssl::SslStream<tokio::net::TcpStream>>>> for RestServer {
|
||||||
for RestServer
|
|
||||||
{
|
|
||||||
type Response = ApiService;
|
type Response = ApiService;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = Pin<Box<dyn Future<Output = Result<ApiService, Error>> + Send>>;
|
type Future = Pin<Box<dyn Future<Output = Result<ApiService, Error>> + Send>>;
|
||||||
|
@ -494,7 +498,6 @@ pub(crate) async fn handle_api_request<Env: RpcEnvironment, S: 'static + BuildHa
|
||||||
Ok(resp)
|
Ok(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn extension_to_content_type(filename: &Path) -> (&'static str, bool) {
|
fn extension_to_content_type(filename: &Path) -> (&'static str, bool) {
|
||||||
if let Some(ext) = filename.extension().and_then(|osstr| osstr.to_str()) {
|
if let Some(ext) = filename.extension().and_then(|osstr| osstr.to_str()) {
|
||||||
return match ext {
|
return match ext {
|
||||||
|
@ -671,7 +674,8 @@ async fn handle_request(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut user_info: Box<dyn UserInformation + Send + Sync> = Box::new(EmptyUserInformation {});
|
let mut user_info: Box<dyn UserInformation + Send + Sync> =
|
||||||
|
Box::new(EmptyUserInformation {});
|
||||||
|
|
||||||
if auth_required {
|
if auth_required {
|
||||||
match api.check_auth(&parts.headers, &method).await {
|
match api.check_auth(&parts.headers, &method).await {
|
||||||
|
@ -730,7 +734,9 @@ async fn handle_request(
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(auth_id) = auth_id {
|
if let Some(auth_id) = auth_id {
|
||||||
response.extensions_mut().insert(AuthStringExtension(auth_id));
|
response
|
||||||
|
.extensions_mut()
|
||||||
|
.insert(AuthStringExtension(auth_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(response);
|
return Ok(response);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use anyhow::{Error};
|
use anyhow::Error;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
@ -40,7 +40,6 @@ lazy_static! {
|
||||||
///
|
///
|
||||||
/// This calls [request_shutdown] when receiving the signal.
|
/// This calls [request_shutdown] when receiving the signal.
|
||||||
pub fn catch_shutdown_signal() -> Result<(), Error> {
|
pub fn catch_shutdown_signal() -> Result<(), Error> {
|
||||||
|
|
||||||
let mut stream = signal(SignalKind::interrupt())?;
|
let mut stream = signal(SignalKind::interrupt())?;
|
||||||
|
|
||||||
let future = async move {
|
let future = async move {
|
||||||
|
@ -49,7 +48,8 @@ pub fn catch_shutdown_signal() -> Result<(), Error> {
|
||||||
SERVER_STATE.lock().unwrap().reload_request = false;
|
SERVER_STATE.lock().unwrap().reload_request = false;
|
||||||
request_shutdown();
|
request_shutdown();
|
||||||
}
|
}
|
||||||
}.boxed();
|
}
|
||||||
|
.boxed();
|
||||||
|
|
||||||
let abort_future = last_worker_future().map_err(|_| {});
|
let abort_future = last_worker_future().map_err(|_| {});
|
||||||
let task = futures::future::select(future, abort_future);
|
let task = futures::future::select(future, abort_future);
|
||||||
|
@ -64,7 +64,6 @@ pub fn catch_shutdown_signal() -> Result<(), Error> {
|
||||||
/// This calls [request_shutdown] when receiving the signal, and tries
|
/// This calls [request_shutdown] when receiving the signal, and tries
|
||||||
/// to restart the server.
|
/// to restart the server.
|
||||||
pub fn catch_reload_signal() -> Result<(), Error> {
|
pub fn catch_reload_signal() -> Result<(), Error> {
|
||||||
|
|
||||||
let mut stream = signal(SignalKind::hangup())?;
|
let mut stream = signal(SignalKind::hangup())?;
|
||||||
|
|
||||||
let future = async move {
|
let future = async move {
|
||||||
|
@ -73,7 +72,8 @@ pub fn catch_reload_signal() -> Result<(), Error> {
|
||||||
SERVER_STATE.lock().unwrap().reload_request = true;
|
SERVER_STATE.lock().unwrap().reload_request = true;
|
||||||
crate::request_shutdown();
|
crate::request_shutdown();
|
||||||
}
|
}
|
||||||
}.boxed();
|
}
|
||||||
|
.boxed();
|
||||||
|
|
||||||
let abort_future = last_worker_future().map_err(|_| {});
|
let abort_future = last_worker_future().map_err(|_| {});
|
||||||
let task = futures::future::select(future, abort_future);
|
let task = futures::future::select(future, abort_future);
|
||||||
|
@ -89,7 +89,6 @@ pub(crate) fn is_reload_request() -> bool {
|
||||||
data.mode == ServerMode::Shutdown && data.reload_request
|
data.mode == ServerMode::Shutdown && data.reload_request
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub(crate) fn server_shutdown() {
|
pub(crate) fn server_shutdown() {
|
||||||
let mut data = SERVER_STATE.lock().unwrap();
|
let mut data = SERVER_STATE.lock().unwrap();
|
||||||
|
|
||||||
|
@ -107,10 +106,7 @@ pub(crate) fn server_shutdown() {
|
||||||
/// Future to signal server shutdown
|
/// Future to signal server shutdown
|
||||||
pub fn shutdown_future() -> impl Future<Output = ()> {
|
pub fn shutdown_future() -> impl Future<Output = ()> {
|
||||||
let mut data = SERVER_STATE.lock().unwrap();
|
let mut data = SERVER_STATE.lock().unwrap();
|
||||||
data
|
data.shutdown_listeners.listen().map(|_| ())
|
||||||
.shutdown_listeners
|
|
||||||
.listen()
|
|
||||||
.map(|_| ())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Future to signal when last worker task finished
|
/// Future to signal when last worker task finished
|
||||||
|
@ -128,7 +124,12 @@ pub(crate) fn set_worker_count(count: usize) {
|
||||||
pub(crate) fn check_last_worker() {
|
pub(crate) fn check_last_worker() {
|
||||||
let mut data = SERVER_STATE.lock().unwrap();
|
let mut data = SERVER_STATE.lock().unwrap();
|
||||||
|
|
||||||
if !(data.mode == ServerMode::Shutdown && data.worker_count == 0 && data.internal_task_count == 0) { return; }
|
if !(data.mode == ServerMode::Shutdown
|
||||||
|
&& data.worker_count == 0
|
||||||
|
&& data.internal_task_count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
data.last_worker_listeners.notify_listeners(Ok(()));
|
data.last_worker_listeners.notify_listeners(Ok(()));
|
||||||
}
|
}
|
||||||
|
@ -147,7 +148,8 @@ where
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let _ = tokio::spawn(task).await; // ignore errors
|
let _ = tokio::spawn(task).await; // ignore errors
|
||||||
|
|
||||||
{ // drop mutex
|
{
|
||||||
|
// drop mutex
|
||||||
let mut data = SERVER_STATE.lock().unwrap();
|
let mut data = SERVER_STATE.lock().unwrap();
|
||||||
if data.internal_task_count > 0 {
|
if data.internal_task_count > 0 {
|
||||||
data.internal_task_count -= 1;
|
data.internal_task_count -= 1;
|
||||||
|
|
|
@ -1,30 +1,30 @@
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::path::PathBuf;
|
use std::io::{BufRead, BufReader, Read, Write};
|
||||||
use std::io::{Read, Write, BufRead, BufReader};
|
|
||||||
use std::panic::UnwindSafe;
|
use std::panic::UnwindSafe;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::time::{SystemTime, Duration};
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
use anyhow::{bail, format_err, Error};
|
use anyhow::{bail, format_err, Error};
|
||||||
use futures::*;
|
use futures::*;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use serde_json::{json, Value};
|
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
use tokio::sync::oneshot;
|
|
||||||
use nix::fcntl::OFlag;
|
use nix::fcntl::OFlag;
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
use tokio::sync::oneshot;
|
||||||
|
|
||||||
use proxmox_sys::linux::procfs;
|
|
||||||
use proxmox_sys::fs::{create_path, replace_file, atomic_open_or_create_file, CreateOptions};
|
|
||||||
use proxmox_lang::try_block;
|
use proxmox_lang::try_block;
|
||||||
use proxmox_schema::upid::UPID;
|
use proxmox_schema::upid::UPID;
|
||||||
|
use proxmox_sys::fs::{atomic_open_or_create_file, create_path, replace_file, CreateOptions};
|
||||||
|
use proxmox_sys::linux::procfs;
|
||||||
|
|
||||||
use proxmox_sys::WorkerTaskContext;
|
|
||||||
use proxmox_sys::logrotate::{LogRotate, LogRotateFiles};
|
use proxmox_sys::logrotate::{LogRotate, LogRotateFiles};
|
||||||
|
use proxmox_sys::WorkerTaskContext;
|
||||||
|
|
||||||
use crate::{CommandSocket, FileLogger, FileLogOptions};
|
use crate::{CommandSocket, FileLogOptions, FileLogger};
|
||||||
|
|
||||||
struct TaskListLockGuard(File);
|
struct TaskListLockGuard(File);
|
||||||
|
|
||||||
|
@ -40,14 +40,13 @@ struct WorkerTaskSetup {
|
||||||
static WORKER_TASK_SETUP: OnceCell<WorkerTaskSetup> = OnceCell::new();
|
static WORKER_TASK_SETUP: OnceCell<WorkerTaskSetup> = OnceCell::new();
|
||||||
|
|
||||||
fn worker_task_setup() -> Result<&'static WorkerTaskSetup, Error> {
|
fn worker_task_setup() -> Result<&'static WorkerTaskSetup, Error> {
|
||||||
WORKER_TASK_SETUP.get()
|
WORKER_TASK_SETUP
|
||||||
|
.get()
|
||||||
.ok_or_else(|| format_err!("WorkerTask library is not initialized"))
|
.ok_or_else(|| format_err!("WorkerTask library is not initialized"))
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WorkerTaskSetup {
|
impl WorkerTaskSetup {
|
||||||
|
|
||||||
fn new(basedir: PathBuf, file_opts: CreateOptions) -> Self {
|
fn new(basedir: PathBuf, file_opts: CreateOptions) -> Self {
|
||||||
|
|
||||||
let mut taskdir = basedir;
|
let mut taskdir = basedir;
|
||||||
taskdir.push("tasks");
|
taskdir.push("tasks");
|
||||||
|
|
||||||
|
@ -74,17 +73,15 @@ impl WorkerTaskSetup {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lock_task_list_files(&self, exclusive: bool) -> Result<TaskListLockGuard, Error> {
|
fn lock_task_list_files(&self, exclusive: bool) -> Result<TaskListLockGuard, Error> {
|
||||||
let options = self.file_opts.clone()
|
let options = self
|
||||||
|
.file_opts
|
||||||
|
.clone()
|
||||||
.perm(nix::sys::stat::Mode::from_bits_truncate(0o660));
|
.perm(nix::sys::stat::Mode::from_bits_truncate(0o660));
|
||||||
|
|
||||||
let timeout = std::time::Duration::new(10, 0);
|
let timeout = std::time::Duration::new(10, 0);
|
||||||
|
|
||||||
let file = proxmox_sys::fs::open_file_locked(
|
let file =
|
||||||
&self.task_lock_fn,
|
proxmox_sys::fs::open_file_locked(&self.task_lock_fn, timeout, exclusive, options)?;
|
||||||
timeout,
|
|
||||||
exclusive,
|
|
||||||
options,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(TaskListLockGuard(file))
|
Ok(TaskListLockGuard(file))
|
||||||
}
|
}
|
||||||
|
@ -99,7 +96,6 @@ impl WorkerTaskSetup {
|
||||||
// atomically read/update the task list, update status of finished tasks
|
// atomically read/update the task list, update status of finished tasks
|
||||||
// new_upid is added to the list when specified.
|
// new_upid is added to the list when specified.
|
||||||
fn update_active_workers(&self, new_upid: Option<&UPID>) -> Result<(), Error> {
|
fn update_active_workers(&self, new_upid: Option<&UPID>) -> Result<(), Error> {
|
||||||
|
|
||||||
let lock = self.lock_task_list_files(true)?;
|
let lock = self.lock_task_list_files(true)?;
|
||||||
|
|
||||||
// TODO remove with 1.x
|
// TODO remove with 1.x
|
||||||
|
@ -121,45 +117,48 @@ impl WorkerTaskSetup {
|
||||||
if !worker_is_active_local(&info.upid) {
|
if !worker_is_active_local(&info.upid) {
|
||||||
// println!("Detected stopped task '{}'", &info.upid_str);
|
// println!("Detected stopped task '{}'", &info.upid_str);
|
||||||
let now = proxmox_time::epoch_i64();
|
let now = proxmox_time::epoch_i64();
|
||||||
let status = upid_read_status(&info.upid).unwrap_or(TaskState::Unknown { endtime: now });
|
let status =
|
||||||
|
upid_read_status(&info.upid).unwrap_or(TaskState::Unknown { endtime: now });
|
||||||
finish_list.push(TaskListInfo {
|
finish_list.push(TaskListInfo {
|
||||||
upid: info.upid,
|
upid: info.upid,
|
||||||
upid_str: info.upid_str,
|
upid_str: info.upid_str,
|
||||||
state: Some(status)
|
state: Some(status),
|
||||||
});
|
});
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(info)
|
Some(info)
|
||||||
}).collect();
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
if let Some(upid) = new_upid {
|
if let Some(upid) = new_upid {
|
||||||
active_list.push(TaskListInfo { upid: upid.clone(), upid_str: upid.to_string(), state: None });
|
active_list.push(TaskListInfo {
|
||||||
|
upid: upid.clone(),
|
||||||
|
upid_str: upid.to_string(),
|
||||||
|
state: None,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let active_raw = render_task_list(&active_list);
|
let active_raw = render_task_list(&active_list);
|
||||||
|
|
||||||
let options = self.file_opts.clone()
|
let options = self
|
||||||
|
.file_opts
|
||||||
|
.clone()
|
||||||
.perm(nix::sys::stat::Mode::from_bits_truncate(0o660));
|
.perm(nix::sys::stat::Mode::from_bits_truncate(0o660));
|
||||||
|
|
||||||
replace_file(
|
replace_file(&self.active_tasks_fn, active_raw.as_bytes(), options, false)?;
|
||||||
&self.active_tasks_fn,
|
|
||||||
active_raw.as_bytes(),
|
|
||||||
options,
|
|
||||||
false,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
finish_list.sort_unstable_by(|a, b| {
|
finish_list.sort_unstable_by(|a, b| match (&a.state, &b.state) {
|
||||||
match (&a.state, &b.state) {
|
|
||||||
(Some(s1), Some(s2)) => s1.cmp(s2),
|
(Some(s1), Some(s2)) => s1.cmp(s2),
|
||||||
(Some(_), None) => std::cmp::Ordering::Less,
|
(Some(_), None) => std::cmp::Ordering::Less,
|
||||||
(None, Some(_)) => std::cmp::Ordering::Greater,
|
(None, Some(_)) => std::cmp::Ordering::Greater,
|
||||||
_ => a.upid.starttime.cmp(&b.upid.starttime),
|
_ => a.upid.starttime.cmp(&b.upid.starttime),
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if !finish_list.is_empty() {
|
if !finish_list.is_empty() {
|
||||||
let options = self.file_opts.clone()
|
let options = self
|
||||||
|
.file_opts
|
||||||
|
.clone()
|
||||||
.perm(nix::sys::stat::Mode::from_bits_truncate(0o660));
|
.perm(nix::sys::stat::Mode::from_bits_truncate(0o660));
|
||||||
|
|
||||||
let mut writer = atomic_open_or_create_file(
|
let mut writer = atomic_open_or_create_file(
|
||||||
|
@ -187,15 +186,17 @@ impl WorkerTaskSetup {
|
||||||
|
|
||||||
// Create task log directory with correct permissions
|
// Create task log directory with correct permissions
|
||||||
fn create_task_log_dirs(&self) -> Result<(), Error> {
|
fn create_task_log_dirs(&self) -> Result<(), Error> {
|
||||||
|
|
||||||
try_block!({
|
try_block!({
|
||||||
let dir_opts = self.file_opts.clone()
|
let dir_opts = self
|
||||||
|
.file_opts
|
||||||
|
.clone()
|
||||||
.perm(nix::sys::stat::Mode::from_bits_truncate(0o755));
|
.perm(nix::sys::stat::Mode::from_bits_truncate(0o755));
|
||||||
|
|
||||||
create_path(&self.taskdir, Some(dir_opts.clone()), Some(dir_opts))?;
|
create_path(&self.taskdir, Some(dir_opts.clone()), Some(dir_opts))?;
|
||||||
// fixme:??? create_path(pbs_buildcfg::PROXMOX_BACKUP_RUN_DIR, None, Some(opts))?;
|
// fixme:??? create_path(pbs_buildcfg::PROXMOX_BACKUP_RUN_DIR, None, Some(opts))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}).map_err(|err: Error| format_err!("unable to create task log dir - {}", err))
|
})
|
||||||
|
.map_err(|err: Error| format_err!("unable to create task log dir - {}", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,7 +204,8 @@ impl WorkerTaskSetup {
|
||||||
pub fn init_worker_tasks(basedir: PathBuf, file_opts: CreateOptions) -> Result<(), Error> {
|
pub fn init_worker_tasks(basedir: PathBuf, file_opts: CreateOptions) -> Result<(), Error> {
|
||||||
let setup = WorkerTaskSetup::new(basedir, file_opts);
|
let setup = WorkerTaskSetup::new(basedir, file_opts);
|
||||||
setup.create_task_log_dirs()?;
|
setup.create_task_log_dirs()?;
|
||||||
WORKER_TASK_SETUP.set(setup)
|
WORKER_TASK_SETUP
|
||||||
|
.set(setup)
|
||||||
.map_err(|_| format_err!("init_worker_tasks failed - already initialized"))
|
.map_err(|_| format_err!("init_worker_tasks failed - already initialized"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,17 +217,11 @@ pub fn rotate_task_log_archive(
|
||||||
max_files: Option<usize>,
|
max_files: Option<usize>,
|
||||||
options: Option<CreateOptions>,
|
options: Option<CreateOptions>,
|
||||||
) -> Result<bool, Error> {
|
) -> Result<bool, Error> {
|
||||||
|
|
||||||
let setup = worker_task_setup()?;
|
let setup = worker_task_setup()?;
|
||||||
|
|
||||||
let _lock = setup.lock_task_list_files(true)?;
|
let _lock = setup.lock_task_list_files(true)?;
|
||||||
|
|
||||||
let mut logrotate = LogRotate::new(
|
let mut logrotate = LogRotate::new(&setup.task_archive_fn, compress, max_files, options)?;
|
||||||
&setup.task_archive_fn,
|
|
||||||
compress,
|
|
||||||
max_files,
|
|
||||||
options,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
logrotate.rotate(size_threshold)
|
logrotate.rotate(size_threshold)
|
||||||
}
|
}
|
||||||
|
@ -237,12 +233,7 @@ pub fn cleanup_old_tasks(compressed: bool) -> Result<(), Error> {
|
||||||
|
|
||||||
let _lock = setup.lock_task_list_files(true)?;
|
let _lock = setup.lock_task_list_files(true)?;
|
||||||
|
|
||||||
let logrotate = LogRotate::new(
|
let logrotate = LogRotate::new(&setup.task_archive_fn, compressed, None, None)?;
|
||||||
&setup.task_archive_fn,
|
|
||||||
compressed,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let mut timestamp = None;
|
let mut timestamp = None;
|
||||||
if let Some(last_file) = logrotate.files().last() {
|
if let Some(last_file) = logrotate.files().last() {
|
||||||
|
@ -265,7 +256,8 @@ pub fn cleanup_old_tasks(compressed: bool) -> Result<(), Error> {
|
||||||
SystemTime::UNIX_EPOCH.checked_add(Duration::from_secs(timestamp as u64))
|
SystemTime::UNIX_EPOCH.checked_add(Duration::from_secs(timestamp as u64))
|
||||||
} else {
|
} else {
|
||||||
SystemTime::UNIX_EPOCH.checked_sub(Duration::from_secs(-timestamp as u64))
|
SystemTime::UNIX_EPOCH.checked_sub(Duration::from_secs(-timestamp as u64))
|
||||||
}.ok_or_else(|| format_err!("could not calculate cutoff time"))?;
|
}
|
||||||
|
.ok_or_else(|| format_err!("could not calculate cutoff time"))?;
|
||||||
|
|
||||||
for i in 0..256 {
|
for i in 0..256 {
|
||||||
let mut path = setup.taskdir.clone();
|
let mut path = setup.taskdir.clone();
|
||||||
|
@ -279,8 +271,8 @@ pub fn cleanup_old_tasks(compressed: bool) -> Result<(), Error> {
|
||||||
|
|
||||||
if modified < cutoff_time {
|
if modified < cutoff_time {
|
||||||
match std::fs::remove_file(path) {
|
match std::fs::remove_file(path) {
|
||||||
Ok(()) => {},
|
Ok(()) => {}
|
||||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {},
|
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {}
|
||||||
Err(err) => bail!("could not remove file: {}", err),
|
Err(err) => bail!("could not remove file: {}", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -291,7 +283,6 @@ pub fn cleanup_old_tasks(compressed: bool) -> Result<(), Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Path to the worker log file
|
/// Path to the worker log file
|
||||||
pub fn upid_log_path(upid: &UPID) -> Result<std::path::PathBuf, Error> {
|
pub fn upid_log_path(upid: &UPID) -> Result<std::path::PathBuf, Error> {
|
||||||
let setup = worker_task_setup()?;
|
let setup = worker_task_setup()?;
|
||||||
|
@ -302,10 +293,11 @@ pub fn upid_log_path(upid: &UPID) -> Result<std::path::PathBuf, Error> {
|
||||||
/// If there is not a single line with at valid datetime, we assume the
|
/// If there is not a single line with at valid datetime, we assume the
|
||||||
/// starttime to be the endtime
|
/// starttime to be the endtime
|
||||||
pub fn upid_read_status(upid: &UPID) -> Result<TaskState, Error> {
|
pub fn upid_read_status(upid: &UPID) -> Result<TaskState, Error> {
|
||||||
|
|
||||||
let setup = worker_task_setup()?;
|
let setup = worker_task_setup()?;
|
||||||
|
|
||||||
let mut status = TaskState::Unknown { endtime: upid.starttime };
|
let mut status = TaskState::Unknown {
|
||||||
|
endtime: upid.starttime,
|
||||||
|
};
|
||||||
|
|
||||||
let path = setup.log_path(upid);
|
let path = setup.log_path(upid);
|
||||||
|
|
||||||
|
@ -350,7 +342,8 @@ pub fn upid_read_status(upid: &UPID) -> Result<TaskState, Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref WORKER_TASK_LIST: Mutex<HashMap<usize, Arc<WorkerTask>>> = Mutex::new(HashMap::new());
|
static ref WORKER_TASK_LIST: Mutex<HashMap<usize, Arc<WorkerTask>>> =
|
||||||
|
Mutex::new(HashMap::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// checks if the task UPID refers to a worker from this process
|
/// checks if the task UPID refers to a worker from this process
|
||||||
|
@ -405,11 +398,13 @@ pub fn worker_is_active_local(upid: &UPID) -> bool {
|
||||||
///
|
///
|
||||||
/// * ``worker-task-status <UPID>``: return true of false, depending on
|
/// * ``worker-task-status <UPID>``: return true of false, depending on
|
||||||
/// whether the worker is running or stopped.
|
/// whether the worker is running or stopped.
|
||||||
pub fn register_task_control_commands(
|
pub fn register_task_control_commands(commando_sock: &mut CommandSocket) -> Result<(), Error> {
|
||||||
commando_sock: &mut CommandSocket,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
fn get_upid(args: Option<&Value>) -> Result<UPID, Error> {
|
fn get_upid(args: Option<&Value>) -> Result<UPID, Error> {
|
||||||
let args = if let Some(args) = args { args } else { bail!("missing args") };
|
let args = if let Some(args) = args {
|
||||||
|
args
|
||||||
|
} else {
|
||||||
|
bail!("missing args")
|
||||||
|
};
|
||||||
let upid = match args.get("upid") {
|
let upid = match args.get("upid") {
|
||||||
Some(Value::String(upid)) => upid.parse::<UPID>()?,
|
Some(Value::String(upid)) => upid.parse::<UPID>()?,
|
||||||
None => bail!("no upid in args"),
|
None => bail!("no upid in args"),
|
||||||
|
@ -454,7 +449,6 @@ pub fn abort_worker_nowait(upid: UPID) {
|
||||||
///
|
///
|
||||||
/// By sending ``worker-task-abort`` to the control socket.
|
/// By sending ``worker-task-abort`` to the control socket.
|
||||||
pub async fn abort_worker(upid: UPID) -> Result<(), Error> {
|
pub async fn abort_worker(upid: UPID) -> Result<(), Error> {
|
||||||
|
|
||||||
let sock = crate::ctrl_sock_from_pid(upid.pid);
|
let sock = crate::ctrl_sock_from_pid(upid.pid);
|
||||||
let cmd = json!({
|
let cmd = json!({
|
||||||
"command": "worker-task-abort",
|
"command": "worker-task-abort",
|
||||||
|
@ -466,7 +460,6 @@ pub async fn abort_worker(upid: UPID) -> Result<(), Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_worker_status_line(line: &str) -> Result<(String, UPID, Option<TaskState>), Error> {
|
fn parse_worker_status_line(line: &str) -> Result<(String, UPID, Option<TaskState>), Error> {
|
||||||
|
|
||||||
let data = line.splitn(3, ' ').collect::<Vec<&str>>();
|
let data = line.splitn(3, ' ').collect::<Vec<&str>>();
|
||||||
|
|
||||||
let len = data.len();
|
let len = data.len();
|
||||||
|
@ -521,7 +514,12 @@ impl TaskState {
|
||||||
let count: u64 = warnings.parse()?;
|
let count: u64 = warnings.parse()?;
|
||||||
Ok(TaskState::Warning { count, endtime })
|
Ok(TaskState::Warning { count, endtime })
|
||||||
} else if !s.is_empty() {
|
} else if !s.is_empty() {
|
||||||
let message = if let Some(err) = s.strip_prefix("ERROR: ") { err } else { s }.to_string();
|
let message = if let Some(err) = s.strip_prefix("ERROR: ") {
|
||||||
|
err
|
||||||
|
} else {
|
||||||
|
s
|
||||||
|
}
|
||||||
|
.to_string();
|
||||||
Ok(TaskState::Error { message, endtime })
|
Ok(TaskState::Error { message, endtime })
|
||||||
} else {
|
} else {
|
||||||
bail!("unable to parse Task Status '{}'", s);
|
bail!("unable to parse Task Status '{}'", s);
|
||||||
|
@ -568,7 +566,12 @@ pub struct TaskListInfo {
|
||||||
fn render_task_line(info: &TaskListInfo) -> String {
|
fn render_task_line(info: &TaskListInfo) -> String {
|
||||||
let mut raw = String::new();
|
let mut raw = String::new();
|
||||||
if let Some(status) = &info.state {
|
if let Some(status) = &info.state {
|
||||||
raw.push_str(&format!("{} {:08X} {}\n", info.upid_str, status.endtime(), status));
|
raw.push_str(&format!(
|
||||||
|
"{} {:08X} {}\n",
|
||||||
|
info.upid_str,
|
||||||
|
status.endtime(),
|
||||||
|
status
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
raw.push_str(&info.upid_str);
|
raw.push_str(&info.upid_str);
|
||||||
raw.push('\n');
|
raw.push('\n');
|
||||||
|
@ -587,8 +590,7 @@ fn render_task_list(list: &[TaskListInfo]) -> String {
|
||||||
|
|
||||||
// note this is not locked, caller has to make sure it is
|
// note this is not locked, caller has to make sure it is
|
||||||
// this will skip (and log) lines that are not valid status lines
|
// this will skip (and log) lines that are not valid status lines
|
||||||
fn read_task_file<R: Read>(reader: R) -> Result<Vec<TaskListInfo>, Error>
|
fn read_task_file<R: Read>(reader: R) -> Result<Vec<TaskListInfo>, Error> {
|
||||||
{
|
|
||||||
let reader = BufReader::new(reader);
|
let reader = BufReader::new(reader);
|
||||||
let mut list = Vec::new();
|
let mut list = Vec::new();
|
||||||
for line in reader.lines() {
|
for line in reader.lines() {
|
||||||
|
@ -597,7 +599,7 @@ fn read_task_file<R: Read>(reader: R) -> Result<Vec<TaskListInfo>, Error>
|
||||||
Ok((upid_str, upid, state)) => list.push(TaskListInfo {
|
Ok((upid_str, upid, state)) => list.push(TaskListInfo {
|
||||||
upid_str,
|
upid_str,
|
||||||
upid,
|
upid,
|
||||||
state
|
state,
|
||||||
}),
|
}),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::warn!("unable to parse worker status '{}' - {}", line, err);
|
log::warn!("unable to parse worker status '{}' - {}", line, err);
|
||||||
|
@ -634,7 +636,6 @@ pub struct TaskListInfoIterator {
|
||||||
impl TaskListInfoIterator {
|
impl TaskListInfoIterator {
|
||||||
/// Creates a new iterator instance.
|
/// Creates a new iterator instance.
|
||||||
pub fn new(active_only: bool) -> Result<Self, Error> {
|
pub fn new(active_only: bool) -> Result<Self, Error> {
|
||||||
|
|
||||||
let setup = worker_task_setup()?;
|
let setup = worker_task_setup()?;
|
||||||
|
|
||||||
let (read_lock, active_list) = {
|
let (read_lock, active_list) = {
|
||||||
|
@ -720,7 +721,6 @@ pub struct WorkerTask {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for WorkerTask {
|
impl std::fmt::Display for WorkerTask {
|
||||||
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
self.upid.fmt(f)
|
self.upid.fmt(f)
|
||||||
}
|
}
|
||||||
|
@ -734,14 +734,12 @@ struct WorkerTaskData {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WorkerTask {
|
impl WorkerTask {
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
worker_type: &str,
|
worker_type: &str,
|
||||||
worker_id: Option<String>,
|
worker_id: Option<String>,
|
||||||
auth_id: String,
|
auth_id: String,
|
||||||
to_stdout: bool,
|
to_stdout: bool,
|
||||||
) -> Result<Arc<Self>, Error> {
|
) -> Result<Arc<Self>, Error> {
|
||||||
|
|
||||||
let setup = worker_task_setup()?;
|
let setup = worker_task_setup()?;
|
||||||
|
|
||||||
let upid = UPID::new(worker_type, worker_id, auth_id)?;
|
let upid = UPID::new(worker_type, worker_id, auth_id)?;
|
||||||
|
@ -751,7 +749,9 @@ impl WorkerTask {
|
||||||
|
|
||||||
path.push(format!("{:02X}", upid.pstart & 255));
|
path.push(format!("{:02X}", upid.pstart & 255));
|
||||||
|
|
||||||
let dir_opts = setup.file_opts.clone()
|
let dir_opts = setup
|
||||||
|
.file_opts
|
||||||
|
.clone()
|
||||||
.perm(nix::sys::stat::Mode::from_bits_truncate(0o755));
|
.perm(nix::sys::stat::Mode::from_bits_truncate(0o755));
|
||||||
|
|
||||||
create_path(&path, None, Some(dir_opts))?;
|
create_path(&path, None, Some(dir_opts))?;
|
||||||
|
@ -800,7 +800,8 @@ impl WorkerTask {
|
||||||
to_stdout: bool,
|
to_stdout: bool,
|
||||||
f: F,
|
f: F,
|
||||||
) -> Result<String, Error>
|
) -> Result<String, Error>
|
||||||
where F: Send + 'static + FnOnce(Arc<WorkerTask>) -> T,
|
where
|
||||||
|
F: Send + 'static + FnOnce(Arc<WorkerTask>) -> T,
|
||||||
T: Send + 'static + Future<Output = Result<(), Error>>,
|
T: Send + 'static + Future<Output = Result<(), Error>>,
|
||||||
{
|
{
|
||||||
let worker = WorkerTask::new(worker_type, worker_id, auth_id, to_stdout)?;
|
let worker = WorkerTask::new(worker_type, worker_id, auth_id, to_stdout)?;
|
||||||
|
@ -822,25 +823,22 @@ impl WorkerTask {
|
||||||
to_stdout: bool,
|
to_stdout: bool,
|
||||||
f: F,
|
f: F,
|
||||||
) -> Result<String, Error>
|
) -> Result<String, Error>
|
||||||
where F: Send + UnwindSafe + 'static + FnOnce(Arc<WorkerTask>) -> Result<(), Error>
|
where
|
||||||
|
F: Send + UnwindSafe + 'static + FnOnce(Arc<WorkerTask>) -> Result<(), Error>,
|
||||||
{
|
{
|
||||||
let worker = WorkerTask::new(worker_type, worker_id, auth_id, to_stdout)?;
|
let worker = WorkerTask::new(worker_type, worker_id, auth_id, to_stdout)?;
|
||||||
let upid_str = worker.upid.to_string();
|
let upid_str = worker.upid.to_string();
|
||||||
|
|
||||||
let _child = std::thread::Builder::new().name(upid_str.clone()).spawn(move || {
|
let _child = std::thread::Builder::new()
|
||||||
|
.name(upid_str.clone())
|
||||||
|
.spawn(move || {
|
||||||
let worker1 = worker.clone();
|
let worker1 = worker.clone();
|
||||||
let result = match std::panic::catch_unwind(move || f(worker1)) {
|
let result = match std::panic::catch_unwind(move || f(worker1)) {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(panic) => {
|
Err(panic) => match panic.downcast::<&str>() {
|
||||||
match panic.downcast::<&str>() {
|
Ok(panic_msg) => Err(format_err!("worker panicked: {}", panic_msg)),
|
||||||
Ok(panic_msg) => {
|
Err(_) => Err(format_err!("worker panicked: unknown type.")),
|
||||||
Err(format_err!("worker panicked: {}", panic_msg))
|
},
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
Err(format_err!("worker panicked: unknown type."))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
worker.log_result(&result);
|
worker.log_result(&result);
|
||||||
|
@ -856,9 +854,15 @@ impl WorkerTask {
|
||||||
let endtime = proxmox_time::epoch_i64();
|
let endtime = proxmox_time::epoch_i64();
|
||||||
|
|
||||||
if let Err(err) = result {
|
if let Err(err) = result {
|
||||||
TaskState::Error { message: err.to_string(), endtime }
|
TaskState::Error {
|
||||||
|
message: err.to_string(),
|
||||||
|
endtime,
|
||||||
|
}
|
||||||
} else if warn_count > 0 {
|
} else if warn_count > 0 {
|
||||||
TaskState::Warning { count: warn_count, endtime }
|
TaskState::Warning {
|
||||||
|
count: warn_count,
|
||||||
|
endtime,
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
TaskState::OK { endtime }
|
TaskState::OK { endtime }
|
||||||
}
|
}
|
||||||
|
@ -900,17 +904,20 @@ impl WorkerTask {
|
||||||
/// Request abort
|
/// Request abort
|
||||||
pub fn request_abort(&self) {
|
pub fn request_abort(&self) {
|
||||||
let prev_abort = self.abort_requested.swap(true, Ordering::SeqCst);
|
let prev_abort = self.abort_requested.swap(true, Ordering::SeqCst);
|
||||||
if !prev_abort { // log abort one time
|
if !prev_abort {
|
||||||
|
// log abort one time
|
||||||
self.log_message("received abort request ...".to_string());
|
self.log_message("received abort request ...".to_string());
|
||||||
}
|
}
|
||||||
// noitify listeners
|
// noitify listeners
|
||||||
let mut data = self.data.lock().unwrap();
|
let mut data = self.data.lock().unwrap();
|
||||||
loop {
|
loop {
|
||||||
match data.abort_listeners.pop() {
|
match data.abort_listeners.pop() {
|
||||||
None => { break; },
|
None => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
Some(ch) => {
|
Some(ch) => {
|
||||||
let _ = ch.send(()); // ignore errors here
|
let _ = ch.send(()); // ignore errors here
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -934,7 +941,6 @@ impl WorkerTask {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WorkerTaskContext for WorkerTask {
|
impl WorkerTaskContext for WorkerTask {
|
||||||
|
|
||||||
fn abort_requested(&self) -> bool {
|
fn abort_requested(&self) -> bool {
|
||||||
self.abort_requested.load(Ordering::SeqCst)
|
self.abort_requested.load(Ordering::SeqCst)
|
||||||
}
|
}
|
||||||
|
@ -963,7 +969,6 @@ impl WorkerTaskContext for WorkerTask {
|
||||||
/// Note: local workers should print logs to stdout, so there is no
|
/// Note: local workers should print logs to stdout, so there is no
|
||||||
/// need to fetch/display logs. We just wait for the worker to finish.
|
/// need to fetch/display logs. We just wait for the worker to finish.
|
||||||
pub async fn wait_for_local_worker(upid_str: &str) -> Result<(), Error> {
|
pub async fn wait_for_local_worker(upid_str: &str) -> Result<(), Error> {
|
||||||
|
|
||||||
let upid: UPID = upid_str.parse()?;
|
let upid: UPID = upid_str.parse()?;
|
||||||
|
|
||||||
let sleep_duration = core::time::Duration::new(0, 100_000_000);
|
let sleep_duration = core::time::Duration::new(0, 100_000_000);
|
||||||
|
|
Loading…
Reference in New Issue