api: rustfmt

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
Thomas Lamprecht 2022-04-14 13:33:01 +02:00
parent 35f151e010
commit dc7a5b3491
53 changed files with 2703 additions and 1864 deletions

View File

@ -3,13 +3,12 @@
use anyhow::{bail, Error};
use hex::FromHex;
use proxmox_router::{Router, RpcEnvironment, Permission};
use proxmox_router::{Permission, Router, RpcEnvironment};
use proxmox_schema::api;
use pbs_api_types::{
Authid, AclListItem, Role,
ACL_PATH_SCHEMA, PROXMOX_CONFIG_DIGEST_SCHEMA, PROXMOX_GROUP_ID_SCHEMA,
ACL_PROPAGATE_SCHEMA, PRIV_SYS_AUDIT, PRIV_PERMISSIONS_MODIFY,
AclListItem, Authid, Role, ACL_PATH_SCHEMA, ACL_PROPAGATE_SCHEMA, PRIV_PERMISSIONS_MODIFY,
PRIV_SYS_AUDIT, PROXMOX_CONFIG_DIGEST_SCHEMA, PROXMOX_GROUP_ID_SCHEMA,
};
use pbs_config::acl::AclTreeNode;
@ -32,15 +31,18 @@ fn extract_acl_node_data(
for (user, roles) in &node.users {
if let Some(auth_id_filter) = auth_id_filter {
if !user.is_token()
|| user.user() != auth_id_filter.user() {
continue;
if !user.is_token() || user.user() != auth_id_filter.user() {
continue;
}
}
for (role, propagate) in roles {
list.push(AclListItem {
path: if path.is_empty() { String::from("/") } else { path.to_string() },
path: if path.is_empty() {
String::from("/")
} else {
path.to_string()
},
propagate: *propagate,
ugid_type: String::from("user"),
ugid: user.to_string(),
@ -55,7 +57,11 @@ fn extract_acl_node_data(
for (role, propagate) in roles {
list.push(AclListItem {
path: if path.is_empty() { String::from("/") } else { path.to_string() },
path: if path.is_empty() {
String::from("/")
} else {
path.to_string()
},
propagate: *propagate,
ugid_type: String::from("group"),
ugid: group.to_string(),
@ -201,8 +207,10 @@ pub fn update_acl(
} else if auth_id.user() != current_auth_id.user() {
bail!("Unprivileged users can only set ACL items for their own API tokens.");
}
},
None => { bail!("Unprivileged user needs to provide auth_id to update ACL item."); },
}
None => {
bail!("Unprivileged user needs to provide auth_id to update ACL item.");
}
};
}
@ -222,18 +230,26 @@ pub fn update_acl(
if let Some(ref _group) = group {
bail!("parameter 'group' - groups are currently not supported.");
} else if let Some(ref auth_id) = auth_id {
if !delete { // Note: we allow to delete non-existent users
if !delete {
// Note: we allow to delete non-existent users
let user_cfg = pbs_config::user::cached_config()?;
if user_cfg.sections.get(&auth_id.to_string()).is_none() {
bail!(format!("no such {}.",
if auth_id.is_token() { "API token" } else { "user" }));
bail!(format!(
"no such {}.",
if auth_id.is_token() {
"API token"
} else {
"user"
}
));
}
}
} else {
bail!("missing 'userid' or 'group' parameter.");
}
if !delete { // Note: we allow to delete entries with invalid path
if !delete {
// Note: we allow to delete entries with invalid path
pbs_config::acl::check_acl_path(&path)?;
}

View File

@ -1,9 +1,9 @@
//! List Authentication domains/realms
use anyhow::{Error};
use anyhow::Error;
use serde_json::{json, Value};
use proxmox_router::{Router, RpcEnvironment, Permission};
use proxmox_router::{Permission, Router, RpcEnvironment};
use proxmox_schema::api;
use pbs_api_types::BasicRealmInfo;
@ -50,5 +50,4 @@ fn list_domains(mut rpcenv: &mut dyn RpcEnvironment) -> Result<Vec<BasicRealmInf
Ok(list)
}
pub const ROUTER: Router = Router::new()
.get(&API_METHOD_LIST_DOMAINS);
pub const ROUTER: Router = Router::new().get(&API_METHOD_LIST_DOMAINS);

View File

@ -6,19 +6,19 @@ use serde_json::{json, Value};
use std::collections::HashMap;
use std::collections::HashSet;
use proxmox_sys::sortable;
use proxmox_router::{
http_err, list_subdirs_api_method, Router, RpcEnvironment, SubdirMap, Permission,
http_err, list_subdirs_api_method, Permission, Router, RpcEnvironment, SubdirMap,
};
use proxmox_schema::api;
use proxmox_sys::sortable;
use pbs_api_types::{
Userid, Authid, PASSWORD_SCHEMA, ACL_PATH_SCHEMA,
PRIVILEGES, PRIV_PERMISSIONS_MODIFY, PRIV_SYS_AUDIT,
Authid, Userid, ACL_PATH_SCHEMA, PASSWORD_SCHEMA, PRIVILEGES, PRIV_PERMISSIONS_MODIFY,
PRIV_SYS_AUDIT,
};
use pbs_tools::ticket::{self, Empty, Ticket};
use pbs_config::acl::AclTreeNode;
use pbs_config::CachedUserInfo;
use pbs_tools::ticket::{self, Empty, Ticket};
use crate::auth_helpers::*;
use crate::config::tfa::TfaChallenge;
@ -193,10 +193,11 @@ pub fn create_ticket(
tfa_challenge: Option<String>,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
use proxmox_rest_server::RestEnvironment;
let env: &RestEnvironment = rpcenv.as_any().downcast_ref::<RestEnvironment>()
let env: &RestEnvironment = rpcenv
.as_any()
.downcast_ref::<RestEnvironment>()
.ok_or_else(|| format_err!("detected worng RpcEnvironment type"))?;
match authenticate_user(&username, &password, path, privs, port, tfa_challenge) {
@ -340,7 +341,7 @@ pub fn list_permissions(
} else {
bail!("not allowed to list permissions of {}", auth_id);
}
},
}
None => current_auth_id,
};

View File

@ -4,31 +4,35 @@ use std::convert::TryFrom;
use anyhow::{bail, format_err, Error};
use serde_json::{json, Value};
use proxmox_sys::sortable;
use proxmox_router::{
http_err, list_subdirs_api_method, Router, RpcEnvironment, SubdirMap, Permission,
http_err, list_subdirs_api_method, Permission, Router, RpcEnvironment, SubdirMap,
};
use proxmox_schema::api;
use proxmox_sys::sortable;
use proxmox_openid::{OpenIdAuthenticator, OpenIdConfig};
use pbs_api_types::{
OpenIdRealmConfig, User, Userid,
EMAIL_SCHEMA, FIRST_NAME_SCHEMA, LAST_NAME_SCHEMA, OPENID_DEFAILT_SCOPE_LIST,
REALM_ID_SCHEMA,
OpenIdRealmConfig, User, Userid, EMAIL_SCHEMA, FIRST_NAME_SCHEMA, LAST_NAME_SCHEMA,
OPENID_DEFAILT_SCOPE_LIST, REALM_ID_SCHEMA,
};
use pbs_buildcfg::PROXMOX_BACKUP_RUN_DIR_M;
use pbs_tools::ticket::Ticket;
use pbs_config::CachedUserInfo;
use pbs_config::open_backup_lockfile;
use pbs_config::CachedUserInfo;
use crate::auth_helpers::*;
use crate::server::ticket::ApiTicket;
fn openid_authenticator(realm_config: &OpenIdRealmConfig, redirect_url: &str) -> Result<OpenIdAuthenticator, Error> {
let scopes: Vec<String> = realm_config.scopes.as_deref().unwrap_or(OPENID_DEFAILT_SCOPE_LIST)
fn openid_authenticator(
realm_config: &OpenIdRealmConfig,
redirect_url: &str,
) -> Result<OpenIdAuthenticator, Error> {
let scopes: Vec<String> = realm_config
.scopes
.as_deref()
.unwrap_or(OPENID_DEFAILT_SCOPE_LIST)
.split(|c: char| c == ',' || c == ';' || char::is_ascii_whitespace(&c))
.filter(|s| !s.is_empty())
.map(String::from)
@ -37,11 +41,10 @@ fn openid_authenticator(realm_config: &OpenIdRealmConfig, redirect_url: &str) ->
let mut acr_values = None;
if let Some(ref list) = realm_config.acr_values {
acr_values = Some(
list
.split(|c: char| c == ',' || c == ';' || char::is_ascii_whitespace(&c))
list.split(|c: char| c == ',' || c == ';' || char::is_ascii_whitespace(&c))
.filter(|s| !s.is_empty())
.map(String::from)
.collect()
.collect(),
);
}
@ -105,7 +108,9 @@ pub fn openid_login(
) -> Result<Value, Error> {
use proxmox_rest_server::RestEnvironment;
let env: &RestEnvironment = rpcenv.as_any().downcast_ref::<RestEnvironment>()
let env: &RestEnvironment = rpcenv
.as_any()
.downcast_ref::<RestEnvironment>()
.ok_or_else(|| format_err!("detected worng RpcEnvironment type"))?;
let user_info = CachedUserInfo::new()?;
@ -113,7 +118,6 @@ pub fn openid_login(
let mut tested_username = None;
let result = proxmox_lang::try_block!({
let (realm, private_auth_state) =
OpenIdAuthenticator::verify_public_auth_state(PROXMOX_BACKUP_RUN_DIR_M!(), &state)?;
@ -157,13 +161,19 @@ pub fn openid_login(
use pbs_config::user;
let _lock = open_backup_lockfile(user::USER_CFG_LOCKFILE, None, true)?;
let firstname = info["given_name"].as_str().map(|n| n.to_string())
let firstname = info["given_name"]
.as_str()
.map(|n| n.to_string())
.filter(|n| FIRST_NAME_SCHEMA.parse_simple_value(n).is_ok());
let lastname = info["family_name"].as_str().map(|n| n.to_string())
let lastname = info["family_name"]
.as_str()
.map(|n| n.to_string())
.filter(|n| LAST_NAME_SCHEMA.parse_simple_value(n).is_ok());
let email = info["email"].as_str().map(|n| n.to_string())
let email = info["email"]
.as_str()
.map(|n| n.to_string())
.filter(|n| EMAIL_SCHEMA.parse_simple_value(n).is_ok());
let user = User {
@ -206,7 +216,7 @@ pub fn openid_login(
if let Err(ref err) = result {
let msg = err.to_string();
env.log_failed_auth(tested_username, &msg);
return Err(http_err!(UNAUTHORIZED, "{}", msg))
return Err(http_err!(UNAUTHORIZED, "{}", msg));
}
result
@ -240,7 +250,6 @@ fn openid_auth_url(
redirect_url: String,
_rpcenv: &mut dyn RpcEnvironment,
) -> Result<String, Error> {
let (domains, _digest) = pbs_config::domains::config()?;
let config: OpenIdRealmConfig = domains.lookup("openid", &realm)?;

View File

@ -7,7 +7,7 @@ use serde_json::{json, Value};
use proxmox_router::{Permission, Router};
use proxmox_schema::api;
use pbs_api_types::{Role, SINGLE_LINE_COMMENT_SCHEMA, PRIVILEGES};
use pbs_api_types::{Role, PRIVILEGES, SINGLE_LINE_COMMENT_SCHEMA};
use pbs_config::acl::ROLE_NAMES;
#[api(
@ -56,5 +56,4 @@ fn list_roles() -> Result<Value, Error> {
Ok(list.into())
}
pub const ROUTER: Router = Router::new()
.get(&API_METHOD_LIST_ROLES);
pub const ROUTER: Router = Router::new().get(&API_METHOD_LIST_ROLES);

View File

@ -1,19 +1,18 @@
//! User Management
use anyhow::{bail, format_err, Error};
use serde::{Serialize, Deserialize};
use hex::FromHex;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::collections::HashMap;
use hex::FromHex;
use proxmox_router::{ApiMethod, Router, RpcEnvironment, SubdirMap, Permission};
use proxmox_router::{ApiMethod, Permission, Router, RpcEnvironment, SubdirMap};
use proxmox_schema::api;
use pbs_api_types::{
PROXMOX_CONFIG_DIGEST_SCHEMA, SINGLE_LINE_COMMENT_SCHEMA, Authid,
Tokenname, UserWithTokens, Userid, User, UserUpdater, ApiToken,
ENABLE_USER_SCHEMA, EXPIRE_USER_SCHEMA, PBS_PASSWORD_SCHEMA,
PRIV_SYS_AUDIT, PRIV_PERMISSIONS_MODIFY,
ApiToken, Authid, Tokenname, User, UserUpdater, UserWithTokens, Userid, ENABLE_USER_SCHEMA,
EXPIRE_USER_SCHEMA, PBS_PASSWORD_SCHEMA, PRIV_PERMISSIONS_MODIFY, PRIV_SYS_AUDIT,
PROXMOX_CONFIG_DIGEST_SCHEMA, SINGLE_LINE_COMMENT_SCHEMA,
};
use pbs_config::token_shadow;
@ -59,7 +58,6 @@ pub fn list_users(
_info: &ApiMethod,
mut rpcenv: &mut dyn RpcEnvironment,
) -> Result<Vec<UserWithTokens>, Error> {
let (config, digest) = pbs_config::user::config()?;
let auth_id: Authid = rpcenv
@ -74,41 +72,34 @@ pub fn list_users(
let top_level_privs = user_info.lookup_privs(&auth_id, &["access", "users"]);
let top_level_allowed = (top_level_privs & PRIV_SYS_AUDIT) != 0;
let filter_by_privs = |user: &User| {
top_level_allowed || user.userid == *userid
};
let filter_by_privs = |user: &User| top_level_allowed || user.userid == *userid;
let list:Vec<User> = config.convert_to_typed_array("user")?;
let list: Vec<User> = config.convert_to_typed_array("user")?;
rpcenv["digest"] = hex::encode(&digest).into();
let iter = list.into_iter().filter(filter_by_privs);
let list = if include_tokens {
let tokens: Vec<ApiToken> = config.convert_to_typed_array("token")?;
let mut user_to_tokens = tokens
.into_iter()
.fold(
HashMap::new(),
|mut map: HashMap<Userid, Vec<ApiToken>>, token: ApiToken| {
let mut user_to_tokens = tokens.into_iter().fold(
HashMap::new(),
|mut map: HashMap<Userid, Vec<ApiToken>>, token: ApiToken| {
if token.tokenid.is_token() {
map
.entry(token.tokenid.user().clone())
map.entry(token.tokenid.user().clone())
.or_default()
.push(token);
}
map
});
iter
.map(|user: User| {
let mut user = new_user_with_tokens(user);
user.tokens = user_to_tokens.remove(&user.userid).unwrap_or_default();
user
})
.collect()
},
);
iter.map(|user: User| {
let mut user = new_user_with_tokens(user);
user.tokens = user_to_tokens.remove(&user.userid).unwrap_or_default();
user
})
.collect()
} else {
iter.map(new_user_with_tokens)
.collect()
iter.map(new_user_with_tokens).collect()
};
Ok(list)
@ -136,14 +127,17 @@ pub fn list_users(
pub fn create_user(
password: Option<String>,
config: User,
rpcenv: &mut dyn RpcEnvironment
rpcenv: &mut dyn RpcEnvironment,
) -> Result<(), Error> {
let _lock = pbs_config::user::lock_config()?;
let (mut section_config, _digest) = pbs_config::user::config()?;
if section_config.sections.get(config.userid.as_str()).is_some() {
if section_config
.sections
.get(config.userid.as_str())
.is_some()
{
bail!("user '{}' already exists.", config.userid);
}
@ -194,7 +188,7 @@ pub fn read_user(userid: Userid, mut rpcenv: &mut dyn RpcEnvironment) -> Result<
#[api()]
#[derive(Serialize, Deserialize)]
#[serde(rename_all="kebab-case")]
#[serde(rename_all = "kebab-case")]
#[allow(non_camel_case_types)]
pub enum DeletableProperty {
/// Delete the comment property.
@ -253,7 +247,6 @@ pub fn update_user(
digest: Option<String>,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<(), Error> {
let _lock = pbs_config::user::lock_config()?;
let (mut config, expected_digest) = pbs_config::user::config()?;
@ -306,11 +299,19 @@ pub fn update_user(
}
if let Some(firstname) = update.firstname {
data.firstname = if firstname.is_empty() { None } else { Some(firstname) };
data.firstname = if firstname.is_empty() {
None
} else {
Some(firstname)
};
}
if let Some(lastname) = update.lastname {
data.lastname = if lastname.is_empty() { None } else { Some(lastname) };
data.lastname = if lastname.is_empty() {
None
} else {
Some(lastname)
};
}
if let Some(email) = update.email {
data.email = if email.is_empty() { None } else { Some(email) };
@ -345,10 +346,9 @@ pub fn update_user(
)]
/// Remove a user from the configuration file.
pub fn delete_user(userid: Userid, digest: Option<String>) -> Result<(), Error> {
let _lock = pbs_config::user::lock_config()?;
let _tfa_lock = crate::config::tfa::write_lock()?;
let (mut config, expected_digest) = pbs_config::user::config()?;
if let Some(ref digest) = digest {
@ -357,7 +357,9 @@ pub fn delete_user(userid: Userid, digest: Option<String>) -> Result<(), Error>
}
match config.sections.get(userid.as_str()) {
Some(_) => { config.sections.remove(userid.as_str()); },
Some(_) => {
config.sections.remove(userid.as_str());
}
None => bail!("user '{}' does not exist.", userid),
}
@ -365,7 +367,7 @@ pub fn delete_user(userid: Userid, digest: Option<String>) -> Result<(), Error>
let authenticator = crate::auth::lookup_authenticator(userid.realm())?;
match authenticator.remove_password(userid.name()) {
Ok(()) => {},
Ok(()) => {}
Err(err) => {
eprintln!(
"error removing password after deleting user {:?}: {}",
@ -417,7 +419,6 @@ pub fn read_token(
_info: &ApiMethod,
mut rpcenv: &mut dyn RpcEnvironment,
) -> Result<ApiToken, Error> {
let (config, digest) = pbs_config::user::config()?;
let tokenid = Authid::from((userid, Some(token_name)));
@ -483,7 +484,6 @@ pub fn generate_token(
expire: Option<i64>,
digest: Option<String>,
) -> Result<Value, Error> {
let _lock = pbs_config::user::lock_config()?;
let (mut config, expected_digest) = pbs_config::user::config()?;
@ -497,7 +497,11 @@ pub fn generate_token(
let tokenid_string = tokenid.to_string();
if config.sections.get(&tokenid_string).is_some() {
bail!("token '{}' for user '{}' already exists.", token_name.as_str(), userid);
bail!(
"token '{}' for user '{}' already exists.",
token_name.as_str(),
userid
);
}
let secret = format!("{:x}", proxmox_uuid::Uuid::generate());
@ -564,7 +568,6 @@ pub fn update_token(
expire: Option<i64>,
digest: Option<String>,
) -> Result<(), Error> {
let _lock = pbs_config::user::lock_config()?;
let (mut config, expected_digest) = pbs_config::user::config()?;
@ -632,7 +635,6 @@ pub fn delete_token(
token_name: Tokenname,
digest: Option<String>,
) -> Result<(), Error> {
let _lock = pbs_config::user::lock_config()?;
let (mut config, expected_digest) = pbs_config::user::config()?;
@ -646,8 +648,14 @@ pub fn delete_token(
let tokenid_string = tokenid.to_string();
match config.sections.get(&tokenid_string) {
Some(_) => { config.sections.remove(&tokenid_string); },
None => bail!("token '{}' of user '{}' does not exist.", token_name.as_str(), userid),
Some(_) => {
config.sections.remove(&tokenid_string);
}
None => bail!(
"token '{}' of user '{}' does not exist.",
token_name.as_str(),
userid
),
}
token_shadow::delete_secret(&tokenid)?;
@ -664,7 +672,7 @@ pub fn delete_token(
}
)]
#[derive(Serialize, Deserialize)]
#[serde(rename_all="kebab-case")]
#[serde(rename_all = "kebab-case")]
/// A Token Entry that contains the token-name
pub struct TokenApiEntry {
/// The Token name
@ -699,20 +707,16 @@ pub fn list_tokens(
_info: &ApiMethod,
mut rpcenv: &mut dyn RpcEnvironment,
) -> Result<Vec<TokenApiEntry>, Error> {
let (config, digest) = pbs_config::user::config()?;
let list:Vec<ApiToken> = config.convert_to_typed_array("token")?;
let list: Vec<ApiToken> = config.convert_to_typed_array("token")?;
rpcenv["digest"] = hex::encode(&digest).into();
let filter_by_owner = |token: ApiToken| {
if token.tokenid.is_token() && token.tokenid.user() == &userid {
let token_name = token.tokenid.tokenname().unwrap().to_owned();
Some(TokenApiEntry {
token_name,
token,
})
Some(TokenApiEntry { token_name, token })
} else {
None
}
@ -733,9 +737,7 @@ const TOKEN_ROUTER: Router = Router::new()
.get(&API_METHOD_LIST_TOKENS)
.match_all("token-name", &TOKEN_ITEM_ROUTER);
const USER_SUBDIRS: SubdirMap = &[
("token", &TOKEN_ROUTER),
];
const USER_SUBDIRS: SubdirMap = &[("token", &TOKEN_ROUTER)];
const USER_ROUTER: Router = Router::new()
.get(&API_METHOD_READ_USER)

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,18 @@
//! Backup Server Administration
use proxmox_router::{Router, SubdirMap};
use proxmox_router::list_subdirs_api_method;
use proxmox_router::{Router, SubdirMap};
pub mod datastore;
pub mod sync;
pub mod verify;
pub mod traffic_control;
pub mod verify;
const SUBDIRS: SubdirMap = &[
("datastore", &datastore::ROUTER),
("sync", &sync::ROUTER),
("traffic-control", &traffic_control::ROUTER),
("verify", &verify::ROUTER)
("verify", &verify::ROUTER),
];
pub const ROUTER: Router = Router::new()

View File

@ -3,32 +3,23 @@
use anyhow::{bail, format_err, Error};
use serde_json::Value;
use proxmox_sys::sortable;
use proxmox_router::{
list_subdirs_api_method, ApiMethod, Router, RpcEnvironment, RpcEnvironmentType, SubdirMap,
Permission,
list_subdirs_api_method, ApiMethod, Permission, Router, RpcEnvironment, RpcEnvironmentType,
SubdirMap,
};
use proxmox_schema::api;
use proxmox_sys::sortable;
use pbs_api_types::{DATASTORE_SCHEMA, JOB_ID_SCHEMA, Authid, SyncJobConfig, SyncJobStatus};
use pbs_api_types::{Authid, SyncJobConfig, SyncJobStatus, DATASTORE_SCHEMA, JOB_ID_SCHEMA};
use pbs_config::sync;
use pbs_config::CachedUserInfo;
use crate::{
api2::{
config::sync::{check_sync_job_modify_access, check_sync_job_read_access},
pull::do_sync_job,
config::sync::{
check_sync_job_modify_access,
check_sync_job_read_access,
},
},
server::{
jobstate::{
Job,
JobState,
compute_schedule_status,
},
},
server::jobstate::{compute_schedule_status, Job, JobState},
};
#[api(
@ -56,7 +47,6 @@ pub fn list_sync_jobs(
_param: Value,
mut rpcenv: &mut dyn RpcEnvironment,
) -> Result<Vec<SyncJobStatus>, Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let user_info = CachedUserInfo::new()?;
@ -72,9 +62,7 @@ pub fn list_sync_jobs(
true
}
})
.filter(|job: &SyncJobConfig| {
check_sync_job_read_access(&user_info, &auth_id, job)
});
.filter(|job: &SyncJobConfig| check_sync_job_read_access(&user_info, &auth_id, job));
let mut list = Vec::new();
@ -84,7 +72,10 @@ pub fn list_sync_jobs(
let status = compute_schedule_status(&last_state, job.schedule.as_deref())?;
list.push(SyncJobStatus { config: job, status });
list.push(SyncJobStatus {
config: job,
status,
});
}
rpcenv["digest"] = hex::encode(&digest).into();
@ -131,19 +122,12 @@ pub fn run_sync_job(
}
#[sortable]
const SYNC_INFO_SUBDIRS: SubdirMap = &[
(
"run",
&Router::new()
.post(&API_METHOD_RUN_SYNC_JOB)
),
];
const SYNC_INFO_SUBDIRS: SubdirMap = &[("run", &Router::new().post(&API_METHOD_RUN_SYNC_JOB))];
const SYNC_INFO_ROUTER: Router = Router::new()
.get(&list_subdirs_api_method!(SYNC_INFO_SUBDIRS))
.subdirs(SYNC_INFO_SUBDIRS);
pub const ROUTER: Router = Router::new()
.get(&API_METHOD_LIST_SYNC_JOBS)
.match_all("id", &SYNC_INFO_ROUTER);

View File

@ -1,12 +1,10 @@
use anyhow::Error;
use serde::{Deserialize, Serialize};
use proxmox_router::{Router, RpcEnvironment, Permission};
use proxmox_router::{Permission, Router, RpcEnvironment};
use proxmox_schema::api;
use pbs_api_types::{
TrafficControlRule, PRIV_SYS_AUDIT,
};
use pbs_api_types::{TrafficControlRule, PRIV_SYS_AUDIT};
use crate::traffic_control_cache::TRAFFIC_CONTROL_CACHE;
@ -18,7 +16,7 @@ use crate::traffic_control_cache::TRAFFIC_CONTROL_CACHE;
},
)]
#[derive(Serialize, Deserialize)]
#[serde(rename_all="kebab-case")]
#[serde(rename_all = "kebab-case")]
/// Traffic control rule config with current rates
pub struct TrafficControlCurrentRate {
#[serde(flatten)]
@ -48,7 +46,6 @@ pub struct TrafficControlCurrentRate {
pub fn show_current_traffic(
mut rpcenv: &mut dyn RpcEnvironment,
) -> Result<Vec<TrafficControlCurrentRate>, Error> {
let (config, digest) = pbs_config::traffic_control::config()?;
let rules: Vec<TrafficControlRule> = config.convert_to_typed_array("rule")?;
@ -62,7 +59,11 @@ pub fn show_current_traffic(
None => (0, 0),
Some(state) => (state.rate_in, state.rate_out),
};
list.push(TrafficControlCurrentRate {config, cur_rate_in, cur_rate_out});
list.push(TrafficControlCurrentRate {
config,
cur_rate_in,
cur_rate_out,
});
}
// also return the configuration digest
@ -71,5 +72,4 @@ pub fn show_current_traffic(
Ok(list)
}
pub const ROUTER: Router = Router::new()
.get(&API_METHOD_SHOW_CURRENT_TRAFFIC);
pub const ROUTER: Router = Router::new().get(&API_METHOD_SHOW_CURRENT_TRAFFIC);

View File

@ -3,29 +3,23 @@
use anyhow::{format_err, Error};
use serde_json::Value;
use proxmox_sys::sortable;
use proxmox_router::{
list_subdirs_api_method, ApiMethod, Router, RpcEnvironment, RpcEnvironmentType, SubdirMap,
Permission,
list_subdirs_api_method, ApiMethod, Permission, Router, RpcEnvironment, RpcEnvironmentType,
SubdirMap,
};
use proxmox_schema::api;
use proxmox_sys::sortable;
use pbs_api_types::{
VerificationJobConfig, VerificationJobStatus, JOB_ID_SCHEMA, Authid,
PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_VERIFY, DATASTORE_SCHEMA,
Authid, VerificationJobConfig, VerificationJobStatus, DATASTORE_SCHEMA, JOB_ID_SCHEMA,
PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_VERIFY,
};
use pbs_config::verify;
use pbs_config::CachedUserInfo;
use crate::{
server::{
do_verification_job,
jobstate::{
Job,
JobState,
compute_schedule_status,
},
},
use crate::server::{
do_verification_job,
jobstate::{compute_schedule_status, Job, JobState},
};
#[api(
@ -84,7 +78,10 @@ pub fn list_verification_jobs(
let status = compute_schedule_status(&last_state, job.schedule.as_deref())?;
list.push(VerificationJobStatus { config: job, status });
list.push(VerificationJobStatus {
config: job,
status,
});
}
rpcenv["digest"] = hex::encode(&digest).into();
@ -117,7 +114,12 @@ pub fn run_verification_job(
let (config, _digest) = verify::config()?;
let verification_job: VerificationJobConfig = config.lookup("verification", &id)?;
user_info.check_privs(&auth_id, &["datastore", &verification_job.store], PRIV_DATASTORE_VERIFY, true)?;
user_info.check_privs(
&auth_id,
&["datastore", &verification_job.store],
PRIV_DATASTORE_VERIFY,
true,
)?;
let job = Job::new("verificationjob", &id)?;
let to_stdout = rpcenv.env_type() == RpcEnvironmentType::CLI;
@ -128,7 +130,8 @@ pub fn run_verification_job(
}
#[sortable]
const VERIFICATION_INFO_SUBDIRS: SubdirMap = &[("run", &Router::new().post(&API_METHOD_RUN_VERIFICATION_JOB))];
const VERIFICATION_INFO_SUBDIRS: SubdirMap =
&[("run", &Router::new().post(&API_METHOD_RUN_VERIFICATION_JOB))];
const VERIFICATION_INFO_ROUTER: Router = Router::new()
.get(&list_subdirs_api_method!(VERIFICATION_INFO_SUBDIRS))

View File

@ -1,20 +1,20 @@
use anyhow::{bail, format_err, Error};
use std::sync::{Arc, Mutex};
use std::collections::HashMap;
use nix::dir::Dir;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use ::serde::{Serialize};
use ::serde::Serialize;
use serde_json::{json, Value};
use proxmox_sys::fs::{replace_file, CreateOptions};
use proxmox_router::{RpcEnvironment, RpcEnvironmentType};
use proxmox_sys::fs::{replace_file, CreateOptions};
use pbs_datastore::{DataStore, DataBlob};
use pbs_api_types::Authid;
use pbs_datastore::backup_info::{BackupDir, BackupInfo};
use pbs_datastore::dynamic_index::DynamicIndexWriter;
use pbs_datastore::fixed_index::FixedIndexWriter;
use pbs_api_types::Authid;
use proxmox_rest_server::{WorkerTask, formatter::*};
use pbs_datastore::{DataBlob, DataStore};
use proxmox_rest_server::{formatter::*, WorkerTask};
use crate::backup::verify_backup_dir_with_lock;
@ -72,7 +72,7 @@ struct FixedWriterState {
}
// key=digest, value=length
type KnownChunksMap = HashMap<[u8;32], u32>;
type KnownChunksMap = HashMap<[u8; 32], u32>;
struct SharedBackupState {
finished: bool,
@ -86,7 +86,6 @@ struct SharedBackupState {
}
impl SharedBackupState {
// Raise error if finished flag is set
fn ensure_unfinished(&self) -> Result<(), Error> {
if self.finished {
@ -102,7 +101,6 @@ impl SharedBackupState {
}
}
/// `RpcEnvironmet` implementation for backup service
#[derive(Clone)]
pub struct BackupEnvironment {
@ -115,7 +113,7 @@ pub struct BackupEnvironment {
pub datastore: Arc<DataStore>,
pub backup_dir: BackupDir,
pub last_backup: Option<BackupInfo>,
state: Arc<Mutex<SharedBackupState>>
state: Arc<Mutex<SharedBackupState>>,
}
impl BackupEnvironment {
@ -126,7 +124,6 @@ impl BackupEnvironment {
datastore: Arc<DataStore>,
backup_dir: BackupDir,
) -> Self {
let state = SharedBackupState {
finished: false,
uid_counter: 0,
@ -188,13 +185,21 @@ impl BackupEnvironment {
};
if size > data.chunk_size {
bail!("fixed writer '{}' - got large chunk ({} > {}", data.name, size, data.chunk_size);
bail!(
"fixed writer '{}' - got large chunk ({} > {}",
data.name,
size,
data.chunk_size
);
}
if size < data.chunk_size {
data.small_chunk_count += 1;
if data.small_chunk_count > 1 {
bail!("fixed writer '{}' - detected multiple end chunks (chunk size too small)", wid);
bail!(
"fixed writer '{}' - detected multiple end chunks (chunk size too small)",
wid
);
}
}
@ -202,7 +207,9 @@ impl BackupEnvironment {
data.upload_stat.count += 1;
data.upload_stat.size += size as u64;
data.upload_stat.compressed_size += compressed_size as u64;
if is_duplicate { data.upload_stat.duplicates += 1; }
if is_duplicate {
data.upload_stat.duplicates += 1;
}
// register chunk
state.known_chunks.insert(digest, size);
@ -235,7 +242,9 @@ impl BackupEnvironment {
data.upload_stat.count += 1;
data.upload_stat.size += size as u64;
data.upload_stat.compressed_size += compressed_size as u64;
if is_duplicate { data.upload_stat.duplicates += 1; }
if is_duplicate {
data.upload_stat.duplicates += 1;
}
// register chunk
state.known_chunks.insert(digest, size);
@ -250,37 +259,71 @@ impl BackupEnvironment {
}
/// Store the writer with an unique ID
pub fn register_dynamic_writer(&self, index: DynamicIndexWriter, name: String) -> Result<usize, Error> {
pub fn register_dynamic_writer(
&self,
index: DynamicIndexWriter,
name: String,
) -> Result<usize, Error> {
let mut state = self.state.lock().unwrap();
state.ensure_unfinished()?;
let uid = state.next_uid();
state.dynamic_writers.insert(uid, DynamicWriterState {
index, name, offset: 0, chunk_count: 0, upload_stat: UploadStatistic::new(),
});
state.dynamic_writers.insert(
uid,
DynamicWriterState {
index,
name,
offset: 0,
chunk_count: 0,
upload_stat: UploadStatistic::new(),
},
);
Ok(uid)
}
/// Store the writer with an unique ID
pub fn register_fixed_writer(&self, index: FixedIndexWriter, name: String, size: usize, chunk_size: u32, incremental: bool) -> Result<usize, Error> {
pub fn register_fixed_writer(
&self,
index: FixedIndexWriter,
name: String,
size: usize,
chunk_size: u32,
incremental: bool,
) -> Result<usize, Error> {
let mut state = self.state.lock().unwrap();
state.ensure_unfinished()?;
let uid = state.next_uid();
state.fixed_writers.insert(uid, FixedWriterState {
index, name, chunk_count: 0, size, chunk_size, small_chunk_count: 0, upload_stat: UploadStatistic::new(), incremental,
});
state.fixed_writers.insert(
uid,
FixedWriterState {
index,
name,
chunk_count: 0,
size,
chunk_size,
small_chunk_count: 0,
upload_stat: UploadStatistic::new(),
incremental,
},
);
Ok(uid)
}
/// Append chunk to dynamic writer
pub fn dynamic_writer_append_chunk(&self, wid: usize, offset: u64, size: u32, digest: &[u8; 32]) -> Result<(), Error> {
pub fn dynamic_writer_append_chunk(
&self,
wid: usize,
offset: u64,
size: u32,
digest: &[u8; 32],
) -> Result<(), Error> {
let mut state = self.state.lock().unwrap();
state.ensure_unfinished()?;
@ -290,10 +333,13 @@ impl BackupEnvironment {
None => bail!("dynamic writer '{}' not registered", wid),
};
if data.offset != offset {
bail!("dynamic writer '{}' append chunk failed - got strange chunk offset ({} != {})",
data.name, data.offset, offset);
bail!(
"dynamic writer '{}' append chunk failed - got strange chunk offset ({} != {})",
data.name,
data.offset,
offset
);
}
data.offset += size as u64;
@ -305,7 +351,13 @@ impl BackupEnvironment {
}
/// Append chunk to fixed writer
pub fn fixed_writer_append_chunk(&self, wid: usize, offset: u64, size: u32, digest: &[u8; 32]) -> Result<(), Error> {
pub fn fixed_writer_append_chunk(
&self,
wid: usize,
offset: u64,
size: u32,
digest: &[u8; 32],
) -> Result<(), Error> {
let mut state = self.state.lock().unwrap();
state.ensure_unfinished()?;
@ -325,7 +377,15 @@ impl BackupEnvironment {
Ok(())
}
fn log_upload_stat(&self, archive_name: &str, csum: &[u8; 32], uuid: &[u8; 16], size: u64, chunk_count: u64, upload_stat: &UploadStatistic) {
fn log_upload_stat(
&self,
archive_name: &str,
csum: &[u8; 32],
uuid: &[u8; 16],
size: u64,
chunk_count: u64,
upload_stat: &UploadStatistic,
) {
self.log(format!("Upload statistics for '{}'", archive_name));
self.log(format!("UUID: {}", hex::encode(uuid)));
self.log(format!("Checksum: {}", hex::encode(csum)));
@ -336,7 +396,11 @@ impl BackupEnvironment {
return;
}
self.log(format!("Upload size: {} ({}%)", upload_stat.size, (upload_stat.size*100)/size));
self.log(format!(
"Upload size: {} ({}%)",
upload_stat.size,
(upload_stat.size * 100) / size
));
// account for zero chunk, which might be uploaded but never used
let client_side_duplicates = if chunk_count < upload_stat.count {
@ -348,17 +412,29 @@ impl BackupEnvironment {
let server_side_duplicates = upload_stat.duplicates;
if (client_side_duplicates + server_side_duplicates) > 0 {
let per = (client_side_duplicates + server_side_duplicates)*100/chunk_count;
self.log(format!("Duplicates: {}+{} ({}%)", client_side_duplicates, server_side_duplicates, per));
let per = (client_side_duplicates + server_side_duplicates) * 100 / chunk_count;
self.log(format!(
"Duplicates: {}+{} ({}%)",
client_side_duplicates, server_side_duplicates, per
));
}
if upload_stat.size > 0 {
self.log(format!("Compression: {}%", (upload_stat.compressed_size*100)/upload_stat.size));
self.log(format!(
"Compression: {}%",
(upload_stat.compressed_size * 100) / upload_stat.size
));
}
}
/// Close dynamic writer
pub fn dynamic_writer_close(&self, wid: usize, chunk_count: u64, size: u64, csum: [u8; 32]) -> Result<(), Error> {
pub fn dynamic_writer_close(
&self,
wid: usize,
chunk_count: u64,
size: u64,
csum: [u8; 32],
) -> Result<(), Error> {
let mut state = self.state.lock().unwrap();
state.ensure_unfinished()?;
@ -369,11 +445,21 @@ impl BackupEnvironment {
};
if data.chunk_count != chunk_count {
bail!("dynamic writer '{}' close failed - unexpected chunk count ({} != {})", data.name, data.chunk_count, chunk_count);
bail!(
"dynamic writer '{}' close failed - unexpected chunk count ({} != {})",
data.name,
data.chunk_count,
chunk_count
);
}
if data.offset != size {
bail!("dynamic writer '{}' close failed - unexpected file size ({} != {})", data.name, data.offset, size);
bail!(
"dynamic writer '{}' close failed - unexpected file size ({} != {})",
data.name,
data.offset,
size
);
}
let uuid = data.index.uuid;
@ -381,10 +467,20 @@ impl BackupEnvironment {
let expected_csum = data.index.close()?;
if csum != expected_csum {
bail!("dynamic writer '{}' close failed - got unexpected checksum", data.name);
bail!(
"dynamic writer '{}' close failed - got unexpected checksum",
data.name
);
}
self.log_upload_stat(&data.name, &csum, &uuid, size, chunk_count, &data.upload_stat);
self.log_upload_stat(
&data.name,
&csum,
&uuid,
size,
chunk_count,
&data.upload_stat,
);
state.file_counter += 1;
state.backup_size += size;
@ -394,7 +490,13 @@ impl BackupEnvironment {
}
/// Close fixed writer
pub fn fixed_writer_close(&self, wid: usize, chunk_count: u64, size: u64, csum: [u8; 32]) -> Result<(), Error> {
pub fn fixed_writer_close(
&self,
wid: usize,
chunk_count: u64,
size: u64,
csum: [u8; 32],
) -> Result<(), Error> {
let mut state = self.state.lock().unwrap();
state.ensure_unfinished()?;
@ -405,18 +507,33 @@ impl BackupEnvironment {
};
if data.chunk_count != chunk_count {
bail!("fixed writer '{}' close failed - received wrong number of chunk ({} != {})", data.name, data.chunk_count, chunk_count);
bail!(
"fixed writer '{}' close failed - received wrong number of chunk ({} != {})",
data.name,
data.chunk_count,
chunk_count
);
}
if !data.incremental {
let expected_count = data.index.index_length();
if chunk_count != (expected_count as u64) {
bail!("fixed writer '{}' close failed - unexpected chunk count ({} != {})", data.name, expected_count, chunk_count);
bail!(
"fixed writer '{}' close failed - unexpected chunk count ({} != {})",
data.name,
expected_count,
chunk_count
);
}
if size != (data.size as u64) {
bail!("fixed writer '{}' close failed - unexpected file size ({} != {})", data.name, data.size, size);
bail!(
"fixed writer '{}' close failed - unexpected file size ({} != {})",
data.name,
data.size,
size
);
}
}
@ -424,10 +541,20 @@ impl BackupEnvironment {
let expected_csum = data.index.close()?;
if csum != expected_csum {
bail!("fixed writer '{}' close failed - got unexpected checksum", data.name);
bail!(
"fixed writer '{}' close failed - got unexpected checksum",
data.name
);
}
self.log_upload_stat(&data.name, &expected_csum, &uuid, size, chunk_count, &data.upload_stat);
self.log_upload_stat(
&data.name,
&expected_csum,
&uuid,
size,
chunk_count,
&data.upload_stat,
);
state.file_counter += 1;
state.backup_size += size;
@ -437,7 +564,6 @@ impl BackupEnvironment {
}
pub fn add_blob(&self, file_name: &str, data: Vec<u8>) -> Result<(), Error> {
let mut path = self.datastore.base_path();
path.push(self.backup_dir.relative_path());
path.push(file_name);
@ -451,7 +577,10 @@ impl BackupEnvironment {
let raw_data = blob.raw_data();
replace_file(&path, raw_data, CreateOptions::new(), false)?;
self.log(format!("add blob {:?} ({} bytes, comp: {})", path, orig_len, blob_len));
self.log(format!(
"add blob {:?} ({} bytes, comp: {})",
path, orig_len, blob_len
));
let mut state = self.state.lock().unwrap();
state.file_counter += 1;
@ -478,9 +607,11 @@ impl BackupEnvironment {
// check for valid manifest and store stats
let stats = serde_json::to_value(state.backup_stat)?;
self.datastore.update_manifest(&self.backup_dir, |manifest| {
manifest.unprotected["chunk_upload_stats"] = stats;
}).map_err(|err| format_err!("unable to update manifest blob - {}", err))?;
self.datastore
.update_manifest(&self.backup_dir, |manifest| {
manifest.unprotected["chunk_upload_stats"] = stats;
})
.map_err(|err| format_err!("unable to update manifest blob - {}", err))?;
if let Some(base) = &self.last_backup {
let path = self.datastore.snapshot_path(&base.backup_dir);
@ -509,11 +640,13 @@ impl BackupEnvironment {
return Ok(());
}
let worker_id = format!("{}:{}/{}/{:08X}",
let worker_id = format!(
"{}:{}/{}/{:08X}",
self.datastore.name(),
self.backup_dir.group().backup_type(),
self.backup_dir.group().backup_id(),
self.backup_dir.backup_time());
self.backup_dir.backup_time()
);
let datastore = self.datastore.clone();
let backup_dir = self.backup_dir.clone();
@ -526,7 +659,6 @@ impl BackupEnvironment {
move |worker| {
worker.log_message("Automatically verifying newly added snapshot");
let verify_worker = crate::backup::VerifyWorker::new(worker.clone(), datastore);
if !verify_backup_dir_with_lock(
&verify_worker,
@ -540,7 +672,8 @@ impl BackupEnvironment {
Ok(())
},
).map(|_| ())
)
.map(|_| ())
}
pub fn log<S: AsRef<str>>(&self, msg: S) {
@ -548,7 +681,9 @@ impl BackupEnvironment {
}
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);
}
}
pub fn format_response(&self, result: Result<Value, Error>) -> Response<Body> {
@ -582,7 +717,6 @@ impl BackupEnvironment {
}
impl RpcEnvironment for BackupEnvironment {
fn result_attrib_mut(&mut self) -> &mut Value {
&mut self.result_attributes
}

View File

@ -2,32 +2,32 @@
use anyhow::{bail, format_err, Error};
use futures::*;
use hex::FromHex;
use hyper::header::{HeaderValue, UPGRADE};
use hyper::http::request::Parts;
use hyper::{Body, Response, Request, StatusCode};
use hyper::{Body, Request, Response, StatusCode};
use serde_json::{json, Value};
use hex::FromHex;
use proxmox_sys::sortable;
use proxmox_router::list_subdirs_api_method;
use proxmox_router::{
ApiResponseFuture, ApiHandler, ApiMethod, Router, RpcEnvironment, SubdirMap, Permission,
ApiHandler, ApiMethod, ApiResponseFuture, Permission, Router, RpcEnvironment, SubdirMap,
};
use proxmox_schema::*;
use proxmox_sys::sortable;
use pbs_api_types::{
Authid, Operation, VerifyState, SnapshotVerifyState,
BACKUP_ID_SCHEMA, BACKUP_TIME_SCHEMA, BACKUP_TYPE_SCHEMA, DATASTORE_SCHEMA,
CHUNK_DIGEST_SCHEMA, PRIV_DATASTORE_BACKUP, BACKUP_ARCHIVE_NAME_SCHEMA,
Authid, Operation, SnapshotVerifyState, VerifyState, BACKUP_ARCHIVE_NAME_SCHEMA,
BACKUP_ID_SCHEMA, BACKUP_TIME_SCHEMA, BACKUP_TYPE_SCHEMA, CHUNK_DIGEST_SCHEMA,
DATASTORE_SCHEMA, PRIV_DATASTORE_BACKUP,
};
use proxmox_sys::fs::lock_dir_noblock_shared;
use pbs_tools::json::{required_array_param, required_integer_param, required_string_param};
use pbs_config::CachedUserInfo;
use pbs_datastore::{DataStore, PROXMOX_BACKUP_PROTOCOL_ID_V1};
use pbs_datastore::backup_info::{BackupDir, BackupGroup, BackupInfo};
use pbs_datastore::index::IndexFile;
use pbs_datastore::manifest::{archive_type, ArchiveType};
use proxmox_rest_server::{WorkerTask, H2Service};
use pbs_datastore::{DataStore, PROXMOX_BACKUP_PROTOCOL_ID_V1};
use pbs_tools::json::{required_array_param, required_integer_param, required_string_param};
use proxmox_rest_server::{H2Service, WorkerTask};
use proxmox_sys::fs::lock_dir_noblock_shared;
mod environment;
use environment::*;
@ -35,8 +35,7 @@ use environment::*;
mod upload_chunk;
use upload_chunk::*;
pub const ROUTER: Router = Router::new()
.upgrade(&API_METHOD_UPGRADE_BACKUP);
pub const ROUTER: Router = Router::new().upgrade(&API_METHOD_UPGRADE_BACKUP);
#[sortable]
pub const API_METHOD_UPGRADE_BACKUP: ApiMethod = ApiMethod::new(
@ -65,269 +64,296 @@ fn upgrade_to_backup_protocol(
_info: &ApiMethod,
rpcenv: Box<dyn RpcEnvironment>,
) -> ApiResponseFuture {
async move {
let debug = param["debug"].as_bool().unwrap_or(false);
let benchmark = param["benchmark"].as_bool().unwrap_or(false);
async move {
let debug = param["debug"].as_bool().unwrap_or(false);
let benchmark = param["benchmark"].as_bool().unwrap_or(false);
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let store = required_string_param(&param, "store")?.to_owned();
let store = required_string_param(&param, "store")?.to_owned();
let user_info = CachedUserInfo::new()?;
user_info.check_privs(
&auth_id,
&["datastore", &store],
PRIV_DATASTORE_BACKUP,
false,
)?;
let user_info = CachedUserInfo::new()?;
user_info.check_privs(&auth_id, &["datastore", &store], PRIV_DATASTORE_BACKUP, false)?;
let datastore = DataStore::lookup_datastore(&store, Some(Operation::Write))?;
let datastore = DataStore::lookup_datastore(&store, Some(Operation::Write))?;
let backup_type = required_string_param(&param, "backup-type")?;
let backup_id = required_string_param(&param, "backup-id")?;
let backup_time = required_integer_param(&param, "backup-time")?;
let backup_type = required_string_param(&param, "backup-type")?;
let backup_id = required_string_param(&param, "backup-id")?;
let backup_time = required_integer_param(&param, "backup-time")?;
let protocols = parts
.headers
.get("UPGRADE")
.ok_or_else(|| format_err!("missing Upgrade header"))?
.to_str()?;
let protocols = parts
.headers
.get("UPGRADE")
.ok_or_else(|| format_err!("missing Upgrade header"))?
.to_str()?;
if protocols != PROXMOX_BACKUP_PROTOCOL_ID_V1!() {
bail!("invalid protocol name");
}
if parts.version >= http::version::Version::HTTP_2 {
bail!("unexpected http version '{:?}' (expected version < 2)", parts.version);
}
let worker_id = format!("{}:{}/{}", store, backup_type, backup_id);
let env_type = rpcenv.env_type();
let backup_group = BackupGroup::new(backup_type, backup_id);
let worker_type = if backup_type == "host" && backup_id == "benchmark" {
if !benchmark {
bail!("unable to run benchmark without --benchmark flags");
if protocols != PROXMOX_BACKUP_PROTOCOL_ID_V1!() {
bail!("invalid protocol name");
}
"benchmark"
} else {
if benchmark {
bail!("benchmark flags is only allowed on 'host/benchmark'");
if parts.version >= http::version::Version::HTTP_2 {
bail!(
"unexpected http version '{:?}' (expected version < 2)",
parts.version
);
}
"backup"
};
// lock backup group to only allow one backup per group at a time
let (owner, _group_guard) = datastore.create_locked_backup_group(&backup_group, &auth_id)?;
let worker_id = format!("{}:{}/{}", store, backup_type, backup_id);
// permission check
let correct_owner = owner == auth_id
|| (owner.is_token()
&& Authid::from(owner.user().clone()) == auth_id);
if !correct_owner && worker_type != "benchmark" {
// only the owner is allowed to create additional snapshots
bail!("backup owner check failed ({} != {})", auth_id, owner);
}
let env_type = rpcenv.env_type();
let last_backup = {
let info = BackupInfo::last_backup(&datastore.base_path(), &backup_group, true).unwrap_or(None);
if let Some(info) = info {
let (manifest, _) = datastore.load_manifest(&info.backup_dir)?;
let verify = manifest.unprotected["verify_state"].clone();
match serde_json::from_value::<SnapshotVerifyState>(verify) {
Ok(verify) => {
match verify.state {
let backup_group = BackupGroup::new(backup_type, backup_id);
let worker_type = if backup_type == "host" && backup_id == "benchmark" {
if !benchmark {
bail!("unable to run benchmark without --benchmark flags");
}
"benchmark"
} else {
if benchmark {
bail!("benchmark flags is only allowed on 'host/benchmark'");
}
"backup"
};
// lock backup group to only allow one backup per group at a time
let (owner, _group_guard) =
datastore.create_locked_backup_group(&backup_group, &auth_id)?;
// permission check
let correct_owner =
owner == auth_id || (owner.is_token() && Authid::from(owner.user().clone()) == auth_id);
if !correct_owner && worker_type != "benchmark" {
// only the owner is allowed to create additional snapshots
bail!("backup owner check failed ({} != {})", auth_id, owner);
}
let last_backup = {
let info = BackupInfo::last_backup(&datastore.base_path(), &backup_group, true)
.unwrap_or(None);
if let Some(info) = info {
let (manifest, _) = datastore.load_manifest(&info.backup_dir)?;
let verify = manifest.unprotected["verify_state"].clone();
match serde_json::from_value::<SnapshotVerifyState>(verify) {
Ok(verify) => match verify.state {
VerifyState::Ok => Some(info),
VerifyState::Failed => None,
},
Err(_) => {
// no verify state found, treat as valid
Some(info)
}
},
Err(_) => {
// no verify state found, treat as valid
Some(info)
}
} else {
None
}
};
let backup_dir = BackupDir::with_group(backup_group, backup_time)?;
let _last_guard = if let Some(last) = &last_backup {
if backup_dir.backup_time() <= last.backup_dir.backup_time() {
bail!("backup timestamp is older than last backup.");
}
// lock last snapshot to prevent forgetting/pruning it during backup
let full_path = datastore.snapshot_path(&last.backup_dir);
Some(lock_dir_noblock_shared(
&full_path,
"snapshot",
"base snapshot is already locked by another operation",
)?)
} else {
None
}
};
};
let backup_dir = BackupDir::with_group(backup_group, backup_time)?;
let _last_guard = if let Some(last) = &last_backup {
if backup_dir.backup_time() <= last.backup_dir.backup_time() {
bail!("backup timestamp is older than last backup.");
let (path, is_new, snap_guard) = datastore.create_locked_backup_dir(&backup_dir)?;
if !is_new {
bail!("backup directory already exists.");
}
// lock last snapshot to prevent forgetting/pruning it during backup
let full_path = datastore.snapshot_path(&last.backup_dir);
Some(lock_dir_noblock_shared(&full_path, "snapshot", "base snapshot is already locked by another operation")?)
} else {
None
};
WorkerTask::spawn(
worker_type,
Some(worker_id),
auth_id.to_string(),
true,
move |worker| {
let mut env = BackupEnvironment::new(
env_type,
auth_id,
worker.clone(),
datastore,
backup_dir,
);
let (path, is_new, snap_guard) = datastore.create_locked_backup_dir(&backup_dir)?;
if !is_new { bail!("backup directory already exists."); }
env.debug = debug;
env.last_backup = last_backup;
env.log(format!(
"starting new {} on datastore '{}': {:?}",
worker_type, store, path
));
WorkerTask::spawn(worker_type, Some(worker_id), auth_id.to_string(), true, move |worker| {
let mut env = BackupEnvironment::new(
env_type, auth_id, worker.clone(), datastore, backup_dir);
let service =
H2Service::new(env.clone(), worker.clone(), &BACKUP_API_ROUTER, debug);
env.debug = debug;
env.last_backup = last_backup;
let abort_future = worker.abort_future();
env.log(format!("starting new {} on datastore '{}': {:?}", worker_type, store, path));
let env2 = env.clone();
let service = H2Service::new(env.clone(), worker.clone(), &BACKUP_API_ROUTER, debug);
let mut req_fut = hyper::upgrade::on(Request::from_parts(parts, req_body))
.map_err(Error::from)
.and_then(move |conn| {
env2.debug("protocol upgrade done");
let abort_future = worker.abort_future();
let mut http = hyper::server::conn::Http::new();
http.http2_only(true);
// increase window size: todo - find optiomal size
let window_size = 32 * 1024 * 1024; // max = (1 << 31) - 2
http.http2_initial_stream_window_size(window_size);
http.http2_initial_connection_window_size(window_size);
http.http2_max_frame_size(4 * 1024 * 1024);
let env2 = env.clone();
let mut req_fut = hyper::upgrade::on(Request::from_parts(parts, req_body))
.map_err(Error::from)
.and_then(move |conn| {
env2.debug("protocol upgrade done");
let mut http = hyper::server::conn::Http::new();
http.http2_only(true);
// increase window size: todo - find optiomal size
let window_size = 32*1024*1024; // max = (1 << 31) - 2
http.http2_initial_stream_window_size(window_size);
http.http2_initial_connection_window_size(window_size);
http.http2_max_frame_size(4*1024*1024);
let env3 = env2.clone();
http.serve_connection(conn, service)
.map(move |result| {
match result {
Err(err) => {
// Avoid Transport endpoint is not connected (os error 107)
// fixme: find a better way to test for that error
if err.to_string().starts_with("connection error") && env3.finished() {
Ok(())
} else {
Err(Error::from(err))
let env3 = env2.clone();
http.serve_connection(conn, service).map(move |result| {
match result {
Err(err) => {
// Avoid Transport endpoint is not connected (os error 107)
// fixme: find a better way to test for that error
if err.to_string().starts_with("connection error")
&& env3.finished()
{
Ok(())
} else {
Err(Error::from(err))
}
}
Ok(()) => Ok(()),
}
Ok(()) => Ok(()),
}
})
});
let mut abort_future = abort_future
.map(|_| Err(format_err!("task aborted")));
})
});
let mut abort_future = abort_future.map(|_| Err(format_err!("task aborted")));
async move {
// keep flock until task ends
let _group_guard = _group_guard;
let snap_guard = snap_guard;
let _last_guard = _last_guard;
async move {
// keep flock until task ends
let _group_guard = _group_guard;
let snap_guard = snap_guard;
let _last_guard = _last_guard;
let res = select!{
req = req_fut => req,
abrt = abort_future => abrt,
};
if benchmark {
env.log("benchmark finished successfully");
proxmox_async::runtime::block_in_place(|| env.remove_backup())?;
return Ok(());
}
let res = select! {
req = req_fut => req,
abrt = abort_future => abrt,
};
if benchmark {
env.log("benchmark finished successfully");
proxmox_async::runtime::block_in_place(|| env.remove_backup())?;
return Ok(());
}
let verify = |env: BackupEnvironment| {
if let Err(err) = env.verify_after_complete(snap_guard) {
env.log(format!(
let verify = |env: BackupEnvironment| {
if let Err(err) = env.verify_after_complete(snap_guard) {
env.log(format!(
"backup finished, but starting the requested verify task failed: {}",
err
));
}
};
match (res, env.ensure_finished()) {
(Ok(_), Ok(())) => {
env.log("backup finished successfully");
verify(env);
Ok(())
}
(Err(err), Ok(())) => {
// ignore errors after finish
env.log(format!("backup had errors but finished: {}", err));
verify(env);
Ok(())
}
(Ok(_), Err(err)) => {
env.log(format!("backup ended and finish failed: {}", err));
env.log("removing unfinished backup");
proxmox_async::runtime::block_in_place(|| env.remove_backup())?;
Err(err)
}
(Err(err), Err(_)) => {
env.log(format!("backup failed: {}", err));
env.log("removing failed backup");
proxmox_async::runtime::block_in_place(|| env.remove_backup())?;
Err(err)
}
}
}
};
},
)?;
match (res, env.ensure_finished()) {
(Ok(_), Ok(())) => {
env.log("backup finished successfully");
verify(env);
Ok(())
},
(Err(err), Ok(())) => {
// ignore errors after finish
env.log(format!("backup had errors but finished: {}", err));
verify(env);
Ok(())
},
(Ok(_), Err(err)) => {
env.log(format!("backup ended and finish failed: {}", err));
env.log("removing unfinished backup");
proxmox_async::runtime::block_in_place(|| env.remove_backup())?;
Err(err)
},
(Err(err), Err(_)) => {
env.log(format!("backup failed: {}", err));
env.log("removing failed backup");
proxmox_async::runtime::block_in_place(|| env.remove_backup())?;
Err(err)
},
}
}
})?;
let response = Response::builder()
.status(StatusCode::SWITCHING_PROTOCOLS)
.header(
UPGRADE,
HeaderValue::from_static(PROXMOX_BACKUP_PROTOCOL_ID_V1!()),
)
.body(Body::empty())?;
let response = Response::builder()
.status(StatusCode::SWITCHING_PROTOCOLS)
.header(UPGRADE, HeaderValue::from_static(PROXMOX_BACKUP_PROTOCOL_ID_V1!()))
.body(Body::empty())?;
Ok(response)
}.boxed()
Ok(response)
}
.boxed()
}
const BACKUP_API_SUBDIRS: SubdirMap = &[
("blob", &Router::new().upload(&API_METHOD_UPLOAD_BLOB)),
(
"blob", &Router::new()
.upload(&API_METHOD_UPLOAD_BLOB)
"dynamic_chunk",
&Router::new().upload(&API_METHOD_UPLOAD_DYNAMIC_CHUNK),
),
(
"dynamic_chunk", &Router::new()
.upload(&API_METHOD_UPLOAD_DYNAMIC_CHUNK)
"dynamic_close",
&Router::new().post(&API_METHOD_CLOSE_DYNAMIC_INDEX),
),
(
"dynamic_close", &Router::new()
.post(&API_METHOD_CLOSE_DYNAMIC_INDEX)
),
(
"dynamic_index", &Router::new()
"dynamic_index",
&Router::new()
.post(&API_METHOD_CREATE_DYNAMIC_INDEX)
.put(&API_METHOD_DYNAMIC_APPEND)
.put(&API_METHOD_DYNAMIC_APPEND),
),
(
"finish", &Router::new()
.post(
&ApiMethod::new(
&ApiHandler::Sync(&finish_backup),
&ObjectSchema::new("Mark backup as finished.", &[])
)
)
"finish",
&Router::new().post(&ApiMethod::new(
&ApiHandler::Sync(&finish_backup),
&ObjectSchema::new("Mark backup as finished.", &[]),
)),
),
(
"fixed_chunk", &Router::new()
.upload(&API_METHOD_UPLOAD_FIXED_CHUNK)
"fixed_chunk",
&Router::new().upload(&API_METHOD_UPLOAD_FIXED_CHUNK),
),
(
"fixed_close", &Router::new()
.post(&API_METHOD_CLOSE_FIXED_INDEX)
"fixed_close",
&Router::new().post(&API_METHOD_CLOSE_FIXED_INDEX),
),
(
"fixed_index", &Router::new()
"fixed_index",
&Router::new()
.post(&API_METHOD_CREATE_FIXED_INDEX)
.put(&API_METHOD_FIXED_APPEND)
.put(&API_METHOD_FIXED_APPEND),
),
(
"previous", &Router::new()
.download(&API_METHOD_DOWNLOAD_PREVIOUS)
"previous",
&Router::new().download(&API_METHOD_DOWNLOAD_PREVIOUS),
),
(
"previous_backup_time", &Router::new()
.get(&API_METHOD_GET_PREVIOUS_BACKUP_TIME)
"previous_backup_time",
&Router::new().get(&API_METHOD_GET_PREVIOUS_BACKUP_TIME),
),
(
"speedtest", &Router::new()
.upload(&API_METHOD_UPLOAD_SPEEDTEST)
"speedtest",
&Router::new().upload(&API_METHOD_UPLOAD_SPEEDTEST),
),
];
@ -340,10 +366,8 @@ pub const API_METHOD_CREATE_DYNAMIC_INDEX: ApiMethod = ApiMethod::new(
&ApiHandler::Sync(&create_dynamic_index),
&ObjectSchema::new(
"Create dynamic chunk index file.",
&sorted!([
("archive-name", false, &BACKUP_ARCHIVE_NAME_SCHEMA),
]),
)
&sorted!([("archive-name", false, &BACKUP_ARCHIVE_NAME_SCHEMA),]),
),
);
fn create_dynamic_index(
@ -351,7 +375,6 @@ fn create_dynamic_index(
_info: &ApiMethod,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
let env: &BackupEnvironment = rpcenv.as_ref();
let name = required_string_param(&param, "archive-name")?.to_owned();
@ -379,14 +402,22 @@ pub const API_METHOD_CREATE_FIXED_INDEX: ApiMethod = ApiMethod::new(
"Create fixed chunk index file.",
&sorted!([
("archive-name", false, &BACKUP_ARCHIVE_NAME_SCHEMA),
("size", false, &IntegerSchema::new("File size.")
.minimum(1)
.schema()
(
"size",
false,
&IntegerSchema::new("File size.").minimum(1).schema()
),
(
"reuse-csum",
true,
&StringSchema::new(
"If set, compare last backup's \
csum and reuse index for incremental backup if it matches."
)
.schema()
),
("reuse-csum", true, &StringSchema::new("If set, compare last backup's \
csum and reuse index for incremental backup if it matches.").schema()),
]),
)
),
);
fn create_fixed_index(
@ -394,7 +425,6 @@ fn create_fixed_index(
_info: &ApiMethod,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
let env: &BackupEnvironment = rpcenv.as_ref();
let name = required_string_param(&param, "archive-name")?.to_owned();
@ -409,7 +439,7 @@ fn create_fixed_index(
let mut path = env.backup_dir.relative_path();
path.push(&archive_name);
let chunk_size = 4096*1024; // todo: ??
let chunk_size = 4096 * 1024; // todo: ??
// do incremental backup if csum is set
let mut reader = None;
@ -436,8 +466,11 @@ fn create_fixed_index(
let (old_csum, _) = index.compute_csum();
let old_csum = hex::encode(&old_csum);
if old_csum != csum {
bail!("expected csum ({}) doesn't match last backup's ({}), cannot do incremental backup",
csum, old_csum);
bail!(
"expected csum ({}) doesn't match last backup's ({}), cannot do incremental backup",
csum,
old_csum
);
}
reader = Some(index);
@ -483,24 +516,28 @@ pub const API_METHOD_DYNAMIC_APPEND: ApiMethod = ApiMethod::new(
&IntegerSchema::new("Corresponding chunk offsets.")
.minimum(0)
.schema()
).schema()
)
.schema()
),
]),
)
),
);
fn dynamic_append (
fn dynamic_append(
param: Value,
_info: &ApiMethod,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
let wid = required_integer_param(&param, "wid")? as usize;
let digest_list = required_array_param(&param, "digest-list")?;
let offset_list = required_array_param(&param, "offset-list")?;
if offset_list.len() != digest_list.len() {
bail!("offset list has wrong length ({} != {})", offset_list.len(), digest_list.len());
bail!(
"offset list has wrong length ({} != {})",
offset_list.len(),
digest_list.len()
);
}
let env: &BackupEnvironment = rpcenv.as_ref();
@ -511,11 +548,16 @@ fn dynamic_append (
let digest_str = item.as_str().unwrap();
let digest = <[u8; 32]>::from_hex(digest_str)?;
let offset = offset_list[i].as_u64().unwrap();
let size = env.lookup_chunk(&digest).ok_or_else(|| format_err!("no such chunk {}", digest_str))?;
let size = env
.lookup_chunk(&digest)
.ok_or_else(|| format_err!("no such chunk {}", digest_str))?;
env.dynamic_writer_append_chunk(wid, offset, size, &digest)?;
env.debug(format!("successfully added chunk {} to dynamic index {} (offset {}, size {})", digest_str, wid, offset, size));
env.debug(format!(
"successfully added chunk {} to dynamic index {} (offset {}, size {})",
digest_str, wid, offset, size
));
}
Ok(Value::Null)
@ -548,24 +590,28 @@ pub const API_METHOD_FIXED_APPEND: ApiMethod = ApiMethod::new(
&IntegerSchema::new("Corresponding chunk offsets.")
.minimum(0)
.schema()
).schema()
)
.schema()
)
]),
)
),
);
fn fixed_append (
fn fixed_append(
param: Value,
_info: &ApiMethod,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
let wid = required_integer_param(&param, "wid")? as usize;
let digest_list = required_array_param(&param, "digest-list")?;
let offset_list = required_array_param(&param, "offset-list")?;
if offset_list.len() != digest_list.len() {
bail!("offset list has wrong length ({} != {})", offset_list.len(), digest_list.len());
bail!(
"offset list has wrong length ({} != {})",
offset_list.len(),
digest_list.len()
);
}
let env: &BackupEnvironment = rpcenv.as_ref();
@ -576,11 +622,16 @@ fn fixed_append (
let digest_str = item.as_str().unwrap();
let digest = <[u8; 32]>::from_hex(digest_str)?;
let offset = offset_list[i].as_u64().unwrap();
let size = env.lookup_chunk(&digest).ok_or_else(|| format_err!("no such chunk {}", digest_str))?;
let size = env
.lookup_chunk(&digest)
.ok_or_else(|| format_err!("no such chunk {}", digest_str))?;
env.fixed_writer_append_chunk(wid, offset, size, &digest)?;
env.debug(format!("successfully added chunk {} to fixed index {} (offset {}, size {})", digest_str, wid, offset, size));
env.debug(format!(
"successfully added chunk {} to fixed index {} (offset {}, size {})",
digest_str, wid, offset, size
));
}
Ok(Value::Null)
@ -603,28 +654,35 @@ pub const API_METHOD_CLOSE_DYNAMIC_INDEX: ApiMethod = ApiMethod::new(
(
"chunk-count",
false,
&IntegerSchema::new("Chunk count. This is used to verify that the server got all chunks.")
.minimum(1)
.schema()
&IntegerSchema::new(
"Chunk count. This is used to verify that the server got all chunks."
)
.minimum(1)
.schema()
),
(
"size",
false,
&IntegerSchema::new("File size. This is used to verify that the server got all data.")
.minimum(1)
.schema()
&IntegerSchema::new(
"File size. This is used to verify that the server got all data."
)
.minimum(1)
.schema()
),
(
"csum",
false,
&StringSchema::new("Digest list checksum.").schema()
),
("csum", false, &StringSchema::new("Digest list checksum.").schema()),
]),
)
),
);
fn close_dynamic_index (
fn close_dynamic_index(
param: Value,
_info: &ApiMethod,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
let wid = required_integer_param(&param, "wid")? as usize;
let chunk_count = required_integer_param(&param, "chunk-count")? as u64;
let size = required_integer_param(&param, "size")? as u64;
@ -673,12 +731,11 @@ pub const API_METHOD_CLOSE_FIXED_INDEX: ApiMethod = ApiMethod::new(
)
);
fn close_fixed_index (
fn close_fixed_index(
param: Value,
_info: &ApiMethod,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
let wid = required_integer_param(&param, "wid")? as usize;
let chunk_count = required_integer_param(&param, "chunk-count")? as u64;
let size = required_integer_param(&param, "size")? as u64;
@ -694,12 +751,11 @@ fn close_fixed_index (
Ok(Value::Null)
}
fn finish_backup (
fn finish_backup(
_param: Value,
_info: &ApiMethod,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
let env: &BackupEnvironment = rpcenv.as_ref();
env.finish_backup()?;
@ -711,10 +767,7 @@ fn finish_backup (
#[sortable]
pub const API_METHOD_GET_PREVIOUS_BACKUP_TIME: ApiMethod = ApiMethod::new(
&ApiHandler::Sync(&get_previous_backup_time),
&ObjectSchema::new(
"Get previous backup time.",
&[],
)
&ObjectSchema::new("Get previous backup time.", &[]),
);
fn get_previous_backup_time(
@ -722,10 +775,12 @@ fn get_previous_backup_time(
_info: &ApiMethod,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
let env: &BackupEnvironment = rpcenv.as_ref();
let backup_time = env.last_backup.as_ref().map(|info| info.backup_dir.backup_time());
let backup_time = env
.last_backup
.as_ref()
.map(|info| info.backup_dir.backup_time());
Ok(json!(backup_time))
}
@ -735,10 +790,8 @@ pub const API_METHOD_DOWNLOAD_PREVIOUS: ApiMethod = ApiMethod::new(
&ApiHandler::AsyncHttp(&download_previous),
&ObjectSchema::new(
"Download archive from previous backup.",
&sorted!([
("archive-name", false, &BACKUP_ARCHIVE_NAME_SCHEMA)
]),
)
&sorted!([("archive-name", false, &BACKUP_ARCHIVE_NAME_SCHEMA)]),
),
);
fn download_previous(
@ -748,7 +801,6 @@ fn download_previous(
_info: &ApiMethod,
rpcenv: Box<dyn RpcEnvironment>,
) -> ApiResponseFuture {
async move {
let env: &BackupEnvironment = rpcenv.as_ref();
@ -772,10 +824,13 @@ fn download_previous(
let index = env.datastore.open_dynamic_reader(&path)?;
Some(Box::new(index))
}
_ => { None }
_ => None,
};
if let Some(index) = index {
env.log(format!("register chunks in '{}' from previous backup.", archive_name));
env.log(format!(
"register chunks in '{}' from previous backup.",
archive_name
));
for pos in 0..index.index_count() {
let info = index.chunk_info(pos).unwrap();
@ -787,5 +842,6 @@ fn download_previous(
env.log(format!("download '{}' from previous backup.", archive_name));
crate::api2::helpers::create_download_response(path).await
}.boxed()
}
.boxed()
}

View File

@ -4,19 +4,19 @@ use std::task::{Context, Poll};
use anyhow::{bail, format_err, Error};
use futures::*;
use hyper::Body;
use hyper::http::request::Parts;
use serde_json::{json, Value};
use hex::FromHex;
use hyper::http::request::Parts;
use hyper::Body;
use serde_json::{json, Value};
use proxmox_sys::sortable;
use proxmox_router::{ApiResponseFuture, ApiHandler, ApiMethod, RpcEnvironment};
use proxmox_router::{ApiHandler, ApiMethod, ApiResponseFuture, RpcEnvironment};
use proxmox_schema::*;
use proxmox_sys::sortable;
use pbs_datastore::{DataStore, DataBlob};
use pbs_api_types::{BACKUP_ARCHIVE_NAME_SCHEMA, CHUNK_DIGEST_SCHEMA};
use pbs_datastore::file_formats::{DataBlobHeader, EncryptedDataBlobHeader};
use pbs_datastore::{DataBlob, DataStore};
use pbs_tools::json::{required_integer_param, required_string_param};
use pbs_api_types::{CHUNK_DIGEST_SCHEMA, BACKUP_ARCHIVE_NAME_SCHEMA};
use super::environment::*;
@ -30,8 +30,21 @@ pub struct UploadChunk {
}
impl UploadChunk {
pub fn new(stream: Body, store: Arc<DataStore>, digest: [u8; 32], size: u32, encoded_size: u32) -> Self {
Self { stream, store, size, encoded_size, raw_data: Some(vec![]), digest }
pub fn new(
stream: Body,
store: Arc<DataStore>,
digest: [u8; 32],
size: u32,
encoded_size: u32,
) -> Self {
Self {
stream,
store,
size,
encoded_size,
raw_data: Some(vec![]),
digest,
}
}
}
@ -77,7 +90,12 @@ impl Future for UploadChunk {
Err(err) => break err,
};
return Poll::Ready(Ok((this.digest, this.size, compressed_size as u32, is_duplicate)))
return Poll::Ready(Ok((
this.digest,
this.size,
compressed_size as u32,
is_duplicate,
)));
} else {
break format_err!("poll upload chunk stream failed - already finished.");
}
@ -94,24 +112,36 @@ pub const API_METHOD_UPLOAD_FIXED_CHUNK: ApiMethod = ApiMethod::new(
&ObjectSchema::new(
"Upload a new chunk.",
&sorted!([
("wid", false, &IntegerSchema::new("Fixed writer ID.")
.minimum(1)
.maximum(256)
.schema()
(
"wid",
false,
&IntegerSchema::new("Fixed writer ID.")
.minimum(1)
.maximum(256)
.schema()
),
("digest", false, &CHUNK_DIGEST_SCHEMA),
("size", false, &IntegerSchema::new("Chunk size.")
.minimum(1)
.maximum(1024*1024*16)
.schema()
(
"size",
false,
&IntegerSchema::new("Chunk size.")
.minimum(1)
.maximum(1024 * 1024 * 16)
.schema()
),
("encoded-size", false, &IntegerSchema::new("Encoded chunk size.")
.minimum((std::mem::size_of::<DataBlobHeader>() as isize)+1)
.maximum(1024*1024*16+(std::mem::size_of::<EncryptedDataBlobHeader>() as isize))
.schema()
(
"encoded-size",
false,
&IntegerSchema::new("Encoded chunk size.")
.minimum((std::mem::size_of::<DataBlobHeader>() as isize) + 1)
.maximum(
1024 * 1024 * 16
+ (std::mem::size_of::<EncryptedDataBlobHeader>() as isize)
)
.schema()
),
]),
)
),
);
fn upload_fixed_chunk(
@ -121,7 +151,6 @@ fn upload_fixed_chunk(
_info: &ApiMethod,
rpcenv: Box<dyn RpcEnvironment>,
) -> ApiResponseFuture {
async move {
let wid = required_integer_param(&param, "wid")? as usize;
let size = required_integer_param(&param, "size")? as u32;
@ -152,24 +181,36 @@ pub const API_METHOD_UPLOAD_DYNAMIC_CHUNK: ApiMethod = ApiMethod::new(
&ObjectSchema::new(
"Upload a new chunk.",
&sorted!([
("wid", false, &IntegerSchema::new("Dynamic writer ID.")
.minimum(1)
.maximum(256)
.schema()
(
"wid",
false,
&IntegerSchema::new("Dynamic writer ID.")
.minimum(1)
.maximum(256)
.schema()
),
("digest", false, &CHUNK_DIGEST_SCHEMA),
("size", false, &IntegerSchema::new("Chunk size.")
.minimum(1)
.maximum(1024*1024*16)
.schema()
(
"size",
false,
&IntegerSchema::new("Chunk size.")
.minimum(1)
.maximum(1024 * 1024 * 16)
.schema()
),
("encoded-size", false, &IntegerSchema::new("Encoded chunk size.")
.minimum((std::mem::size_of::<DataBlobHeader>() as isize) +1)
.maximum(1024*1024*16+(std::mem::size_of::<EncryptedDataBlobHeader>() as isize))
.schema()
(
"encoded-size",
false,
&IntegerSchema::new("Encoded chunk size.")
.minimum((std::mem::size_of::<DataBlobHeader>() as isize) + 1)
.maximum(
1024 * 1024 * 16
+ (std::mem::size_of::<EncryptedDataBlobHeader>() as isize)
)
.schema()
),
]),
)
),
);
fn upload_dynamic_chunk(
@ -179,7 +220,6 @@ fn upload_dynamic_chunk(
_info: &ApiMethod,
rpcenv: Box<dyn RpcEnvironment>,
) -> ApiResponseFuture {
async move {
let wid = required_integer_param(&param, "wid")? as usize;
let size = required_integer_param(&param, "size")? as u32;
@ -191,8 +231,7 @@ fn upload_dynamic_chunk(
let env: &BackupEnvironment = rpcenv.as_ref();
let (digest, size, compressed_size, is_duplicate) =
UploadChunk::new(req_body, env.datastore.clone(), digest, size, encoded_size)
.await?;
UploadChunk::new(req_body, env.datastore.clone(), digest, size, encoded_size).await?;
env.register_dynamic_chunk(wid, digest, size, compressed_size, is_duplicate)?;
let digest_str = hex::encode(&digest);
@ -200,12 +239,13 @@ fn upload_dynamic_chunk(
let result = Ok(json!(digest_str));
Ok(env.format_response(result))
}.boxed()
}
.boxed()
}
pub const API_METHOD_UPLOAD_SPEEDTEST: ApiMethod = ApiMethod::new(
&ApiHandler::AsyncHttp(&upload_speedtest),
&ObjectSchema::new("Test upload speed.", &[])
&ObjectSchema::new("Test upload speed.", &[]),
);
fn upload_speedtest(
@ -215,9 +255,7 @@ fn upload_speedtest(
_info: &ApiMethod,
rpcenv: Box<dyn RpcEnvironment>,
) -> ApiResponseFuture {
async move {
let result = req_body
.map_err(Error::from)
.try_fold(0, |size: usize, chunk| {
@ -237,7 +275,8 @@ fn upload_speedtest(
}
let env: &BackupEnvironment = rpcenv.as_ref();
Ok(env.format_response(Ok(Value::Null)))
}.boxed()
}
.boxed()
}
#[sortable]
@ -247,13 +286,19 @@ pub const API_METHOD_UPLOAD_BLOB: ApiMethod = ApiMethod::new(
"Upload binary blob file.",
&sorted!([
("file-name", false, &BACKUP_ARCHIVE_NAME_SCHEMA),
("encoded-size", false, &IntegerSchema::new("Encoded blob size.")
.minimum(std::mem::size_of::<DataBlobHeader>() as isize)
.maximum(1024*1024*16+(std::mem::size_of::<EncryptedDataBlobHeader>() as isize))
.schema()
(
"encoded-size",
false,
&IntegerSchema::new("Encoded blob size.")
.minimum(std::mem::size_of::<DataBlobHeader>() as isize)
.maximum(
1024 * 1024 * 16
+ (std::mem::size_of::<EncryptedDataBlobHeader>() as isize)
)
.schema()
)
]),
)
),
);
fn upload_blob(
@ -263,7 +308,6 @@ fn upload_blob(
_info: &ApiMethod,
rpcenv: Box<dyn RpcEnvironment>,
) -> ApiResponseFuture {
async move {
let file_name = required_string_param(&param, "file-name")?.to_owned();
let encoded_size = required_integer_param(&param, "encoded-size")? as usize;
@ -283,11 +327,16 @@ fn upload_blob(
.await?;
if encoded_size != data.len() {
bail!("got blob with unexpected length ({} != {})", encoded_size, data.len());
bail!(
"got blob with unexpected length ({} != {})",
encoded_size,
data.len()
);
}
env.add_blob(&file_name, data)?;
Ok(env.format_response(Ok(Value::Null)))
}.boxed()
}
.boxed()
}

View File

@ -1,15 +1,12 @@
use proxmox_router::{Router, SubdirMap};
use proxmox_router::list_subdirs_api_method;
use proxmox_router::{Router, SubdirMap};
use proxmox_sys::sortable;
pub mod tfa;
pub mod openid;
pub mod tfa;
#[sortable]
const SUBDIRS: SubdirMap = &sorted!([
("openid", &openid::ROUTER),
("tfa", &tfa::ROUTER),
]);
const SUBDIRS: SubdirMap = &sorted!([("openid", &openid::ROUTER), ("tfa", &tfa::ROUTER),]);
pub const ROUTER: Router = Router::new()
.get(&list_subdirs_api_method!(SUBDIRS))

View File

@ -1,16 +1,15 @@
/// Configure OpenId realms
use anyhow::Error;
use serde_json::Value;
use ::serde::{Deserialize, Serialize};
/// Configure OpenId realms
use anyhow::Error;
use hex::FromHex;
use serde_json::Value;
use proxmox_router::{http_bail, Router, RpcEnvironment, Permission};
use proxmox_router::{http_bail, Permission, Router, RpcEnvironment};
use proxmox_schema::{api, param_bail};
use pbs_api_types::{
OpenIdRealmConfig, OpenIdRealmConfigUpdater,
PROXMOX_CONFIG_DIGEST_SCHEMA, REALM_ID_SCHEMA, PRIV_SYS_AUDIT, PRIV_REALM_ALLOCATE,
OpenIdRealmConfig, OpenIdRealmConfigUpdater, PRIV_REALM_ALLOCATE, PRIV_SYS_AUDIT,
PROXMOX_CONFIG_DIGEST_SCHEMA, REALM_ID_SCHEMA,
};
use pbs_config::domains;
@ -33,7 +32,6 @@ pub fn list_openid_realms(
_param: Value,
mut rpcenv: &mut dyn RpcEnvironment,
) -> Result<Vec<OpenIdRealmConfig>, Error> {
let (config, digest) = domains::config()?;
let list = config.convert_to_typed_array("openid")?;
@ -59,14 +57,13 @@ pub fn list_openid_realms(
)]
/// Create a new OpenId realm
pub fn create_openid_realm(config: OpenIdRealmConfig) -> Result<(), Error> {
let _lock = domains::lock_config()?;
let (mut domains, _digest) = domains::config()?;
if config.realm == "pbs" ||
config.realm == "pam" ||
domains.sections.get(&config.realm).is_some()
if config.realm == "pbs"
|| config.realm == "pam"
|| domains.sections.get(&config.realm).is_some()
{
param_bail!("realm", "realm '{}' already exists.", config.realm);
}
@ -101,7 +98,6 @@ pub fn delete_openid_realm(
digest: Option<String>,
_rpcenv: &mut dyn RpcEnvironment,
) -> Result<(), Error> {
let _lock = domains::lock_config()?;
let (mut domains, expected_digest) = domains::config()?;
@ -111,7 +107,7 @@ pub fn delete_openid_realm(
crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
}
if domains.sections.remove(&realm).is_none() {
if domains.sections.remove(&realm).is_none() {
http_bail!(NOT_FOUND, "realm '{}' does not exist.", realm);
}
@ -138,7 +134,6 @@ pub fn read_openid_realm(
realm: String,
mut rpcenv: &mut dyn RpcEnvironment,
) -> Result<OpenIdRealmConfig, Error> {
let (domains, digest) = domains::config()?;
let config = domains.lookup("openid", &realm)?;
@ -150,7 +145,7 @@ pub fn read_openid_realm(
#[api()]
#[derive(Serialize, Deserialize)]
#[serde(rename_all="kebab-case")]
#[serde(rename_all = "kebab-case")]
#[allow(non_camel_case_types)]
/// Deletable property name
pub enum DeletableProperty {
@ -206,7 +201,6 @@ pub fn update_openid_realm(
digest: Option<String>,
_rpcenv: &mut dyn RpcEnvironment,
) -> Result<(), Error> {
let _lock = domains::lock_config()?;
let (mut domains, expected_digest) = domains::config()?;
@ -221,12 +215,24 @@ pub fn update_openid_realm(
if let Some(delete) = delete {
for delete_prop in delete {
match delete_prop {
DeletableProperty::client_key => { config.client_key = None; },
DeletableProperty::comment => { config.comment = None; },
DeletableProperty::autocreate => { config.autocreate = None; },
DeletableProperty::scopes => { config.scopes = None; },
DeletableProperty::prompt => { config.prompt = None; },
DeletableProperty::acr_values => { config.acr_values = None; },
DeletableProperty::client_key => {
config.client_key = None;
}
DeletableProperty::comment => {
config.comment = None;
}
DeletableProperty::autocreate => {
config.autocreate = None;
}
DeletableProperty::scopes => {
config.scopes = None;
}
DeletableProperty::prompt => {
config.prompt = None;
}
DeletableProperty::acr_values => {
config.acr_values = None;
}
}
}
}
@ -240,14 +246,28 @@ pub fn update_openid_realm(
}
}
if let Some(issuer_url) = update.issuer_url { config.issuer_url = issuer_url; }
if let Some(client_id) = update.client_id { config.client_id = client_id; }
if let Some(issuer_url) = update.issuer_url {
config.issuer_url = issuer_url;
}
if let Some(client_id) = update.client_id {
config.client_id = client_id;
}
if update.client_key.is_some() { config.client_key = update.client_key; }
if update.autocreate.is_some() { config.autocreate = update.autocreate; }
if update.scopes.is_some() { config.scopes = update.scopes; }
if update.prompt.is_some() { config.prompt = update.prompt; }
if update.acr_values.is_some() { config.acr_values = update.acr_values; }
if update.client_key.is_some() {
config.client_key = update.client_key;
}
if update.autocreate.is_some() {
config.autocreate = update.autocreate;
}
if update.scopes.is_some() {
config.scopes = update.scopes;
}
if update.prompt.is_some() {
config.prompt = update.prompt;
}
if update.acr_values.is_some() {
config.acr_values = update.acr_values;
}
domains.set_data(&realm, "openid", &config)?;

View File

@ -5,10 +5,10 @@ use std::sync::{Arc, Mutex};
use std::time::SystemTime;
use anyhow::{bail, format_err, Error};
use hex::FromHex;
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use hex::FromHex;
use proxmox_router::{
http_bail, list_subdirs_api_method, Permission, Router, RpcEnvironment, SubdirMap,

View File

@ -1,18 +1,17 @@
use anyhow::Error;
use ::serde::{Deserialize, Serialize};
use serde_json::Value;
use anyhow::Error;
use hex::FromHex;
use serde_json::Value;
use proxmox_router::{http_bail, Router, RpcEnvironment, Permission};
use proxmox_router::{http_bail, Permission, Router, RpcEnvironment};
use proxmox_schema::{api, param_bail};
use pbs_api_types::{
Authid, ScsiTapeChanger, ScsiTapeChangerUpdater, LtoTapeDrive,
PROXMOX_CONFIG_DIGEST_SCHEMA, CHANGER_NAME_SCHEMA, SLOT_ARRAY_SCHEMA,
PRIV_TAPE_AUDIT, PRIV_TAPE_MODIFY,
Authid, LtoTapeDrive, ScsiTapeChanger, ScsiTapeChangerUpdater, CHANGER_NAME_SCHEMA,
PRIV_TAPE_AUDIT, PRIV_TAPE_MODIFY, PROXMOX_CONFIG_DIGEST_SCHEMA, SLOT_ARRAY_SCHEMA,
};
use pbs_config::CachedUserInfo;
use pbs_tape::linux_list_drives::{linux_tape_changer_list, check_drive_path};
use pbs_tape::linux_list_drives::{check_drive_path, linux_tape_changer_list};
#[api(
protected: true,
@ -30,7 +29,6 @@ use pbs_tape::linux_list_drives::{linux_tape_changer_list, check_drive_path};
)]
/// Create a new changer device
pub fn create_changer(config: ScsiTapeChanger) -> Result<(), Error> {
let _lock = pbs_config::drive::lock()?;
let (mut section_config, _digest) = pbs_config::drive::config()?;
@ -47,7 +45,12 @@ pub fn create_changer(config: ScsiTapeChanger) -> Result<(), Error> {
}
if changer.path == config.path {
param_bail!("path", "Path '{}' already in use by '{}'", config.path, changer.name);
param_bail!(
"path",
"Path '{}' already in use by '{}'",
config.path,
changer.name
);
}
}
@ -79,7 +82,6 @@ pub fn get_config(
_param: Value,
mut rpcenv: &mut dyn RpcEnvironment,
) -> Result<ScsiTapeChanger, Error> {
let (config, digest) = pbs_config::drive::config()?;
let data: ScsiTapeChanger = config.lookup("changer", &name)?;
@ -176,7 +178,6 @@ pub fn update_changer(
digest: Option<String>,
_param: Value,
) -> Result<(), Error> {
let _lock = pbs_config::drive::lock()?;
let (mut config, expected_digest) = pbs_config::drive::config()?;
@ -244,7 +245,6 @@ pub fn update_changer(
)]
/// Delete a tape changer configuration
pub fn delete_changer(name: String, _param: Value) -> Result<(), Error> {
let _lock = pbs_config::drive::lock()?;
let (mut config, _digest) = pbs_config::drive::config()?;
@ -252,18 +252,31 @@ pub fn delete_changer(name: String, _param: Value) -> Result<(), Error> {
match config.sections.get(&name) {
Some((section_type, _)) => {
if section_type != "changer" {
param_bail!("name", "Entry '{}' exists, but is not a changer device", name);
param_bail!(
"name",
"Entry '{}' exists, but is not a changer device",
name
);
}
config.sections.remove(&name);
},
None => http_bail!(NOT_FOUND, "Delete changer '{}' failed - no such entry", name),
}
None => http_bail!(
NOT_FOUND,
"Delete changer '{}' failed - no such entry",
name
),
}
let drive_list: Vec<LtoTapeDrive> = config.convert_to_typed_array("lto")?;
for drive in drive_list {
if let Some(changer) = drive.changer {
if changer == name {
param_bail!("name", "Delete changer '{}' failed - used by drive '{}'", name, drive.name);
param_bail!(
"name",
"Delete changer '{}' failed - used by drive '{}'",
name,
drive.name
);
}
}
}
@ -278,7 +291,6 @@ const ITEM_ROUTER: Router = Router::new()
.put(&API_METHOD_UPDATE_CHANGER)
.delete(&API_METHOD_DELETE_CHANGER);
pub const ROUTER: Router = Router::new()
.get(&API_METHOD_LIST_CHANGERS)
.post(&API_METHOD_CREATE_CHANGER)

View File

@ -1,31 +1,27 @@
use std::path::PathBuf;
use anyhow::Error;
use serde_json::Value;
use ::serde::{Deserialize, Serialize};
use anyhow::Error;
use hex::FromHex;
use serde_json::Value;
use proxmox_router::{http_bail, Router, RpcEnvironment, RpcEnvironmentType, Permission};
use proxmox_router::{http_bail, Permission, Router, RpcEnvironment, RpcEnvironmentType};
use proxmox_schema::{api, param_bail, ApiType};
use proxmox_section_config::SectionConfigData;
use proxmox_sys::WorkerTaskContext;
use pbs_datastore::chunk_store::ChunkStore;
use pbs_config::BackupLockGuard;
use pbs_api_types::{
Authid, DatastoreNotify,
DATASTORE_SCHEMA, PROXMOX_CONFIG_DIGEST_SCHEMA,
Authid, DataStoreConfig, DataStoreConfigUpdater, DatastoreNotify, DATASTORE_SCHEMA,
PRIV_DATASTORE_ALLOCATE, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_MODIFY,
DataStoreConfig, DataStoreConfigUpdater,
PROXMOX_CONFIG_DIGEST_SCHEMA,
};
use pbs_config::BackupLockGuard;
use pbs_datastore::chunk_store::ChunkStore;
use crate::api2::admin::{sync::list_sync_jobs, verify::list_verification_jobs};
use crate::api2::config::sync::delete_sync_job;
use crate::api2::config::tape_backup_job::{delete_tape_backup_job, list_tape_backup_jobs};
use crate::api2::config::verify::delete_verification_job;
use crate::api2::config::tape_backup_job::{list_tape_backup_jobs, delete_tape_backup_job};
use crate::api2::admin::{
sync::list_sync_jobs,
verify::list_verification_jobs,
};
use pbs_config::CachedUserInfo;
use proxmox_rest_server::WorkerTask;
@ -50,7 +46,6 @@ pub fn list_datastores(
_param: Value,
mut rpcenv: &mut dyn RpcEnvironment,
) -> Result<Vec<DataStoreConfig>, Error> {
let (config, digest) = pbs_config::datastore::config()?;
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
@ -58,7 +53,7 @@ pub fn list_datastores(
rpcenv["digest"] = hex::encode(&digest).into();
let list:Vec<DataStoreConfig> = config.convert_to_typed_array("datastore")?;
let list: Vec<DataStoreConfig> = config.convert_to_typed_array("datastore")?;
let filter_by_privs = |store: &DataStoreConfig| {
let user_privs = user_info.lookup_privs(&auth_id, &["datastore", &store.name]);
(user_privs & PRIV_DATASTORE_AUDIT) != 0
@ -76,7 +71,13 @@ pub(crate) fn do_create_datastore(
let path: PathBuf = datastore.path.clone().into();
let backup_user = pbs_config::backup_user()?;
let _store = ChunkStore::create(&datastore.name, path, backup_user.uid, backup_user.gid, worker)?;
let _store = ChunkStore::create(
&datastore.name,
path,
backup_user.uid,
backup_user.gid,
worker,
)?;
config.set_data(&datastore.name, "datastore", &datastore)?;
@ -107,7 +108,6 @@ pub fn create_datastore(
config: DataStoreConfig,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<String, Error> {
let lock = pbs_config::datastore::lock_config()?;
let (section_config, _digest) = pbs_config::datastore::config()?;
@ -124,7 +124,7 @@ pub fn create_datastore(
Some(config.name.to_string()),
auth_id.to_string(),
to_stdout,
move |worker| do_create_datastore(lock, section_config, config, Some(&worker)),
move |worker| do_create_datastore(lock, section_config, config, Some(&worker)),
)
}
@ -156,7 +156,7 @@ pub fn read_datastore(
#[api()]
#[derive(Serialize, Deserialize)]
#[serde(rename_all="kebab-case")]
#[serde(rename_all = "kebab-case")]
#[allow(non_camel_case_types)]
/// Deletable property name
pub enum DeletableProperty {
@ -226,7 +226,6 @@ pub fn update_datastore(
delete: Option<Vec<DeletableProperty>>,
digest: Option<String>,
) -> Result<(), Error> {
let _lock = pbs_config::datastore::lock_config()?;
// pass/compare digest
@ -239,23 +238,51 @@ pub fn update_datastore(
let mut data: DataStoreConfig = config.lookup("datastore", &name)?;
if let Some(delete) = delete {
if let Some(delete) = delete {
for delete_prop in delete {
match delete_prop {
DeletableProperty::comment => { data.comment = None; },
DeletableProperty::gc_schedule => { data.gc_schedule = None; },
DeletableProperty::prune_schedule => { data.prune_schedule = None; },
DeletableProperty::keep_last => { data.keep_last = None; },
DeletableProperty::keep_hourly => { data.keep_hourly = None; },
DeletableProperty::keep_daily => { data.keep_daily = None; },
DeletableProperty::keep_weekly => { data.keep_weekly = None; },
DeletableProperty::keep_monthly => { data.keep_monthly = None; },
DeletableProperty::keep_yearly => { data.keep_yearly = None; },
DeletableProperty::verify_new => { data.verify_new = None; },
DeletableProperty::notify => { data.notify = None; },
DeletableProperty::notify_user => { data.notify_user = None; },
DeletableProperty::tuning => { data.tuning = None; },
DeletableProperty::maintenance_mode => { data.maintenance_mode = None; },
DeletableProperty::comment => {
data.comment = None;
}
DeletableProperty::gc_schedule => {
data.gc_schedule = None;
}
DeletableProperty::prune_schedule => {
data.prune_schedule = None;
}
DeletableProperty::keep_last => {
data.keep_last = None;
}
DeletableProperty::keep_hourly => {
data.keep_hourly = None;
}
DeletableProperty::keep_daily => {
data.keep_daily = None;
}
DeletableProperty::keep_weekly => {
data.keep_weekly = None;
}
DeletableProperty::keep_monthly => {
data.keep_monthly = None;
}
DeletableProperty::keep_yearly => {
data.keep_yearly = None;
}
DeletableProperty::verify_new => {
data.verify_new = None;
}
DeletableProperty::notify => {
data.notify = None;
}
DeletableProperty::notify_user => {
data.notify_user = None;
}
DeletableProperty::tuning => {
data.tuning = None;
}
DeletableProperty::maintenance_mode => {
data.maintenance_mode = None;
}
}
}
}
@ -281,29 +308,54 @@ pub fn update_datastore(
data.prune_schedule = update.prune_schedule;
}
if update.keep_last.is_some() { data.keep_last = update.keep_last; }
if update.keep_hourly.is_some() { data.keep_hourly = update.keep_hourly; }
if update.keep_daily.is_some() { data.keep_daily = update.keep_daily; }
if update.keep_weekly.is_some() { data.keep_weekly = update.keep_weekly; }
if update.keep_monthly.is_some() { data.keep_monthly = update.keep_monthly; }
if update.keep_yearly.is_some() { data.keep_yearly = update.keep_yearly; }
if update.keep_last.is_some() {
data.keep_last = update.keep_last;
}
if update.keep_hourly.is_some() {
data.keep_hourly = update.keep_hourly;
}
if update.keep_daily.is_some() {
data.keep_daily = update.keep_daily;
}
if update.keep_weekly.is_some() {
data.keep_weekly = update.keep_weekly;
}
if update.keep_monthly.is_some() {
data.keep_monthly = update.keep_monthly;
}
if update.keep_yearly.is_some() {
data.keep_yearly = update.keep_yearly;
}
if let Some(notify_str) = update.notify {
let value = DatastoreNotify::API_SCHEMA.parse_property_string(&notify_str)?;
let notify: DatastoreNotify = serde_json::from_value(value)?;
if let DatastoreNotify { gc: None, verify: None, sync: None } = notify {
if let DatastoreNotify {
gc: None,
verify: None,
sync: None,
} = notify
{
data.notify = None;
} else {
data.notify = Some(notify_str);
}
}
if update.verify_new.is_some() { data.verify_new = update.verify_new; }
if update.verify_new.is_some() {
data.verify_new = update.verify_new;
}
if update.notify_user.is_some() { data.notify_user = update.notify_user; }
if update.notify_user.is_some() {
data.notify_user = update.notify_user;
}
if update.tuning.is_some() { data.tuning = update.tuning; }
if update.tuning.is_some() {
data.tuning = update.tuning;
}
if update.maintenance_mode.is_some() { data.maintenance_mode = update.maintenance_mode; }
if update.maintenance_mode.is_some() {
data.maintenance_mode = update.maintenance_mode;
}
config.set_data(&name, "datastore", &data)?;
@ -352,7 +404,6 @@ pub async fn delete_datastore(
digest: Option<String>,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<(), Error> {
let _lock = pbs_config::datastore::lock_config()?;
let (mut config, expected_digest) = pbs_config::datastore::config()?;
@ -363,7 +414,9 @@ pub async fn delete_datastore(
}
match config.sections.get(&name) {
Some(_) => { config.sections.remove(&name); },
Some(_) => {
config.sections.remove(&name);
}
None => http_bail!(NOT_FOUND, "datastore '{}' does not exist.", name),
}
@ -376,7 +429,10 @@ pub async fn delete_datastore(
}
let tape_jobs = list_tape_backup_jobs(Value::Null, rpcenv)?;
for job_config in tape_jobs.into_iter().filter(|config| config.setup.store == name) {
for job_config in tape_jobs
.into_iter()
.filter(|config| config.setup.store == name)
{
delete_tape_backup_job(job_config.id, None, rpcenv)?;
}
}

View File

@ -1,18 +1,18 @@
use anyhow::{format_err, Error};
use ::serde::{Deserialize, Serialize};
use serde_json::Value;
use anyhow::{format_err, Error};
use hex::FromHex;
use serde_json::Value;
use proxmox_router::{http_bail, Router, RpcEnvironment, Permission};
use proxmox_router::{http_bail, Permission, Router, RpcEnvironment};
use proxmox_schema::{api, param_bail};
use pbs_api_types::{
Authid, LtoTapeDrive, LtoTapeDriveUpdater, ScsiTapeChanger,
PROXMOX_CONFIG_DIGEST_SCHEMA, DRIVE_NAME_SCHEMA, PRIV_TAPE_AUDIT, PRIV_TAPE_MODIFY,
Authid, LtoTapeDrive, LtoTapeDriveUpdater, ScsiTapeChanger, DRIVE_NAME_SCHEMA, PRIV_TAPE_AUDIT,
PRIV_TAPE_MODIFY, PROXMOX_CONFIG_DIGEST_SCHEMA,
};
use pbs_config::CachedUserInfo;
use pbs_tape::linux_list_drives::{lto_tape_device_list, check_drive_path};
use pbs_tape::linux_list_drives::{check_drive_path, lto_tape_device_list};
#[api(
protected: true,
@ -30,7 +30,6 @@ use pbs_tape::linux_list_drives::{lto_tape_device_list, check_drive_path};
)]
/// Create a new drive
pub fn create_drive(config: LtoTapeDrive) -> Result<(), Error> {
let _lock = pbs_config::drive::lock()?;
let (mut section_config, _digest) = pbs_config::drive::config()?;
@ -46,7 +45,12 @@ pub fn create_drive(config: LtoTapeDrive) -> Result<(), Error> {
param_bail!("name", "Entry '{}' already exists", config.name);
}
if drive.path == config.path {
param_bail!("path", "Path '{}' already used in drive '{}'", config.path, drive.name);
param_bail!(
"path",
"Path '{}' already used in drive '{}'",
config.path,
drive.name
);
}
}
@ -78,7 +82,6 @@ pub fn get_config(
_param: Value,
mut rpcenv: &mut dyn RpcEnvironment,
) -> Result<LtoTapeDrive, Error> {
let (config, digest) = pbs_config::drive::config()?;
let data: LtoTapeDrive = config.lookup("lto", &name)?;
@ -176,9 +179,8 @@ pub fn update_drive(
update: LtoTapeDriveUpdater,
delete: Option<Vec<DeletableProperty>>,
digest: Option<String>,
_param: Value,
_param: Value,
) -> Result<(), Error> {
let _lock = pbs_config::drive::lock()?;
let (mut config, expected_digest) = pbs_config::drive::config()?;
@ -196,8 +198,10 @@ pub fn update_drive(
DeletableProperty::changer => {
data.changer = None;
data.changer_drivenum = None;
},
DeletableProperty::changer_drivenum => { data.changer_drivenum = None; },
}
DeletableProperty::changer_drivenum => {
data.changer_drivenum = None;
}
}
}
}
@ -218,7 +222,10 @@ pub fn update_drive(
data.changer_drivenum = None;
} else {
if data.changer.is_none() {
param_bail!("changer", format_err!("Option 'changer-drivenum' requires option 'changer'."));
param_bail!(
"changer",
format_err!("Option 'changer-drivenum' requires option 'changer'.")
);
}
data.changer_drivenum = Some(changer_drivenum);
}
@ -246,7 +253,6 @@ pub fn update_drive(
)]
/// Delete a drive configuration
pub fn delete_drive(name: String, _param: Value) -> Result<(), Error> {
let _lock = pbs_config::drive::lock()?;
let (mut config, _digest) = pbs_config::drive::config()?;
@ -254,10 +260,14 @@ pub fn delete_drive(name: String, _param: Value) -> Result<(), Error> {
match config.sections.get(&name) {
Some((section_type, _)) => {
if section_type != "lto" {
param_bail!("name", "Entry '{}' exists, but is not a lto tape drive", name);
param_bail!(
"name",
"Entry '{}' exists, but is not a lto tape drive",
name
);
}
config.sections.remove(&name);
},
}
None => http_bail!(NOT_FOUND, "Delete drive '{}' failed - no such drive", name),
}
@ -271,7 +281,6 @@ const ITEM_ROUTER: Router = Router::new()
.put(&API_METHOD_UPDATE_DRIVE)
.delete(&API_METHOD_DELETE_DRIVE);
pub const ROUTER: Router = Router::new()
.get(&API_METHOD_LIST_DRIVES)
.post(&API_METHOD_CREATE_DRIVE)

View File

@ -1,12 +1,12 @@
use anyhow::Error;
use ::serde::{Deserialize, Serialize};
use anyhow::Error;
use proxmox_router::{http_bail, Router, RpcEnvironment, Permission};
use proxmox_router::{http_bail, Permission, Router, RpcEnvironment};
use proxmox_schema::{api, param_bail};
use pbs_api_types::{
Authid, MediaPoolConfig, MediaPoolConfigUpdater, MEDIA_POOL_NAME_SCHEMA,
PRIV_TAPE_AUDIT, PRIV_TAPE_MODIFY,
Authid, MediaPoolConfig, MediaPoolConfigUpdater, MEDIA_POOL_NAME_SCHEMA, PRIV_TAPE_AUDIT,
PRIV_TAPE_MODIFY,
};
use pbs_config::CachedUserInfo;
@ -26,10 +26,7 @@ use pbs_config::CachedUserInfo;
},
)]
/// Create a new media pool
pub fn create_pool(
config: MediaPoolConfig,
) -> Result<(), Error> {
pub fn create_pool(config: MediaPoolConfig) -> Result<(), Error> {
let _lock = pbs_config::media_pool::lock()?;
let (mut section_config, _digest) = pbs_config::media_pool::config()?;
@ -59,9 +56,7 @@ pub fn create_pool(
},
)]
/// List media pools
pub fn list_pools(
mut rpcenv: &mut dyn RpcEnvironment,
) -> Result<Vec<MediaPoolConfig>, Error> {
pub fn list_pools(mut rpcenv: &mut dyn RpcEnvironment) -> Result<Vec<MediaPoolConfig>, Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let user_info = CachedUserInfo::new()?;
@ -69,7 +64,7 @@ pub fn list_pools(
let list = config.convert_to_typed_array::<MediaPoolConfig>("pool")?;
let list = list
let list = list
.into_iter()
.filter(|pool| {
let privs = user_info.lookup_privs(&auth_id, &["tape", "pool", &pool.name]);
@ -99,7 +94,6 @@ pub fn list_pools(
)]
/// Get media pool configuration
pub fn get_config(name: String) -> Result<MediaPoolConfig, Error> {
let (config, _digest) = pbs_config::media_pool::config()?;
let data: MediaPoolConfig = config.lookup("pool", &name)?;
@ -155,7 +149,6 @@ pub fn update_pool(
update: MediaPoolConfigUpdater,
delete: Option<Vec<DeletableProperty>>,
) -> Result<(), Error> {
let _lock = pbs_config::media_pool::lock()?;
let (mut config, _digest) = pbs_config::media_pool::config()?;
@ -165,19 +158,37 @@ pub fn update_pool(
if let Some(delete) = delete {
for delete_prop in delete {
match delete_prop {
DeletableProperty::allocation => { data.allocation = None; },
DeletableProperty::retention => { data.retention = None; },
DeletableProperty::template => { data.template = None; },
DeletableProperty::encrypt => { data.encrypt = None; },
DeletableProperty::comment => { data.comment = None; },
DeletableProperty::allocation => {
data.allocation = None;
}
DeletableProperty::retention => {
data.retention = None;
}
DeletableProperty::template => {
data.template = None;
}
DeletableProperty::encrypt => {
data.encrypt = None;
}
DeletableProperty::comment => {
data.comment = None;
}
}
}
}
if update.allocation.is_some() { data.allocation = update.allocation; }
if update.retention.is_some() { data.retention = update.retention; }
if update.template.is_some() { data.template = update.template; }
if update.encrypt.is_some() { data.encrypt = update.encrypt; }
if update.allocation.is_some() {
data.allocation = update.allocation;
}
if update.retention.is_some() {
data.retention = update.retention;
}
if update.template.is_some() {
data.template = update.template;
}
if update.encrypt.is_some() {
data.encrypt = update.encrypt;
}
if let Some(comment) = update.comment {
let comment = comment.trim();
@ -210,13 +221,14 @@ pub fn update_pool(
)]
/// Delete a media pool configuration
pub fn delete_pool(name: String) -> Result<(), Error> {
let _lock = pbs_config::media_pool::lock()?;
let (mut config, _digest) = pbs_config::media_pool::config()?;
match config.sections.get(&name) {
Some(_) => { config.sections.remove(&name); },
Some(_) => {
config.sections.remove(&name);
}
None => http_bail!(NOT_FOUND, "delete pool '{}' failed - no such pool", name),
}
@ -230,7 +242,6 @@ const ITEM_ROUTER: Router = Router::new()
.put(&API_METHOD_UPDATE_POOL)
.delete(&API_METHOD_DELETE_POOL);
pub const ROUTER: Router = Router::new()
.get(&API_METHOD_LIST_POOLS)
.post(&API_METHOD_CREATE_POOL)

View File

@ -1,20 +1,20 @@
//! Backup Server Configuration
use proxmox_router::{Router, SubdirMap};
use proxmox_router::list_subdirs_api_method;
use proxmox_router::{Router, SubdirMap};
pub mod access;
pub mod acme;
pub mod changer;
pub mod datastore;
pub mod drive;
pub mod media_pool;
pub mod remote;
pub mod sync;
pub mod verify;
pub mod drive;
pub mod changer;
pub mod media_pool;
pub mod tape_encryption_keys;
pub mod tape_backup_job;
pub mod tape_encryption_keys;
pub mod traffic_control;
pub mod verify;
const SUBDIRS: SubdirMap = &[
("access", &access::ROUTER),

View File

@ -1,20 +1,20 @@
use anyhow::{bail, format_err, Error};
use proxmox_sys::sortable;
use proxmox_router::SubdirMap;
use proxmox_router::list_subdirs_api_method;
use serde_json::Value;
use ::serde::{Deserialize, Serialize};
use anyhow::{bail, format_err, Error};
use hex::FromHex;
use proxmox_router::list_subdirs_api_method;
use proxmox_router::SubdirMap;
use proxmox_sys::sortable;
use serde_json::Value;
use proxmox_router::{http_bail, http_err, ApiMethod, Router, RpcEnvironment, Permission};
use proxmox_router::{http_bail, http_err, ApiMethod, Permission, Router, RpcEnvironment};
use proxmox_schema::{api, param_bail};
use pbs_client::{HttpClient, HttpClientOptions};
use pbs_api_types::{
REMOTE_ID_SCHEMA, REMOTE_PASSWORD_SCHEMA, Remote, RemoteConfig, RemoteConfigUpdater,
Authid, PROXMOX_CONFIG_DIGEST_SCHEMA, DATASTORE_SCHEMA, GroupListItem,
DataStoreListItem, RateLimitConfig, SyncJobConfig, PRIV_REMOTE_AUDIT, PRIV_REMOTE_MODIFY,
Authid, DataStoreListItem, GroupListItem, RateLimitConfig, Remote, RemoteConfig,
RemoteConfigUpdater, SyncJobConfig, DATASTORE_SCHEMA, PRIV_REMOTE_AUDIT, PRIV_REMOTE_MODIFY,
PROXMOX_CONFIG_DIGEST_SCHEMA, REMOTE_ID_SCHEMA, REMOTE_PASSWORD_SCHEMA,
};
use pbs_client::{HttpClient, HttpClientOptions};
use pbs_config::sync;
use pbs_config::CachedUserInfo;
@ -84,12 +84,7 @@ pub fn list_remotes(
},
)]
/// Create new remote.
pub fn create_remote(
name: String,
config: RemoteConfig,
password: String,
) -> Result<(), Error> {
pub fn create_remote(name: String, config: RemoteConfig, password: String) -> Result<(), Error> {
let _lock = pbs_config::remote::lock_config()?;
let (mut section_config, _digest) = pbs_config::remote::config()?;
@ -98,7 +93,11 @@ pub fn create_remote(
param_bail!("name", "remote '{}' already exists.", name);
}
let remote = Remote { name: name.clone(), config, password };
let remote = Remote {
name: name.clone(),
config,
password,
};
section_config.set_data(&name, "remote", &remote)?;
@ -188,7 +187,6 @@ pub fn update_remote(
delete: Option<Vec<DeletableProperty>>,
digest: Option<String>,
) -> Result<(), Error> {
let _lock = pbs_config::remote::lock_config()?;
let (mut config, expected_digest) = pbs_config::remote::config()?;
@ -203,9 +201,15 @@ pub fn update_remote(
if let Some(delete) = delete {
for delete_prop in delete {
match delete_prop {
DeletableProperty::comment => { data.config.comment = None; },
DeletableProperty::fingerprint => { data.config.fingerprint = None; },
DeletableProperty::port => { data.config.port = None; },
DeletableProperty::comment => {
data.config.comment = None;
}
DeletableProperty::fingerprint => {
data.config.fingerprint = None;
}
DeletableProperty::port => {
data.config.port = None;
}
}
}
}
@ -218,12 +222,22 @@ pub fn update_remote(
data.config.comment = Some(comment);
}
}
if let Some(host) = update.host { data.config.host = host; }
if update.port.is_some() { data.config.port = update.port; }
if let Some(auth_id) = update.auth_id { data.config.auth_id = auth_id; }
if let Some(password) = password { data.password = password; }
if let Some(host) = update.host {
data.config.host = host;
}
if update.port.is_some() {
data.config.port = update.port;
}
if let Some(auth_id) = update.auth_id {
data.config.auth_id = auth_id;
}
if let Some(password) = password {
data.password = password;
}
if update.fingerprint.is_some() { data.config.fingerprint = update.fingerprint; }
if update.fingerprint.is_some() {
data.config.fingerprint = update.fingerprint;
}
config.set_data(&name, "remote", &data)?;
@ -251,13 +265,18 @@ pub fn update_remote(
)]
/// Remove a remote from the configuration file.
pub fn delete_remote(name: String, digest: Option<String>) -> Result<(), Error> {
let (sync_jobs, _) = sync::config()?;
let job_list: Vec<SyncJobConfig> = sync_jobs.convert_to_typed_array("sync")?;
let job_list: Vec<SyncJobConfig> = sync_jobs.convert_to_typed_array("sync")?;
for job in job_list {
if job.remote == name {
param_bail!("name", "remote '{}' is used by sync job '{}' (datastore '{}')", name, job.id, job.store);
param_bail!(
"name",
"remote '{}' is used by sync job '{}' (datastore '{}')",
name,
job.id,
job.store
);
}
}
@ -271,7 +290,9 @@ pub fn delete_remote(name: String, digest: Option<String>) -> Result<(), Error>
}
match config.sections.get(&name) {
Some(_) => { config.sections.remove(&name); },
Some(_) => {
config.sections.remove(&name);
}
None => http_bail!(NOT_FOUND, "remote '{}' does not exist.", name),
}
@ -285,7 +306,10 @@ pub async fn remote_client(
remote: &Remote,
limit: Option<RateLimitConfig>,
) -> Result<HttpClient, Error> {
let mut options = HttpClientOptions::new_non_interactive(remote.password.clone(), remote.config.fingerprint.clone());
let mut options = HttpClientOptions::new_non_interactive(
remote.password.clone(),
remote.config.fingerprint.clone(),
);
if let Some(limit) = limit {
options = options.rate_limit(limit);
@ -295,15 +319,22 @@ pub async fn remote_client(
&remote.config.host,
remote.config.port.unwrap_or(8007),
&remote.config.auth_id,
options)?;
let _auth_info = client.login() // make sure we can auth
options,
)?;
let _auth_info = client
.login() // make sure we can auth
.await
.map_err(|err| format_err!("remote connection to '{}' failed - {}", remote.config.host, err))?;
.map_err(|err| {
format_err!(
"remote connection to '{}' failed - {}",
remote.config.host,
err
)
})?;
Ok(client)
}
#[api(
input: {
properties: {
@ -327,15 +358,15 @@ pub async fn scan_remote_datastores(name: String) -> Result<Vec<DataStoreListIte
let remote: Remote = remote_config.lookup("remote", &name)?;
let map_remote_err = |api_err| {
http_err!(INTERNAL_SERVER_ERROR,
"failed to scan remote '{}' - {}",
&name,
api_err)
http_err!(
INTERNAL_SERVER_ERROR,
"failed to scan remote '{}' - {}",
&name,
api_err
)
};
let client = remote_client(&remote, None)
.await
.map_err(map_remote_err)?;
let client = remote_client(&remote, None).await.map_err(map_remote_err)?;
let api_res = client
.get("api2/json/admin/datastore", None)
.await
@ -377,15 +408,15 @@ pub async fn scan_remote_groups(name: String, store: String) -> Result<Vec<Group
let remote: Remote = remote_config.lookup("remote", &name)?;
let map_remote_err = |api_err| {
http_err!(INTERNAL_SERVER_ERROR,
"failed to scan remote '{}' - {}",
&name,
api_err)
http_err!(
INTERNAL_SERVER_ERROR,
"failed to scan remote '{}' - {}",
&name,
api_err
)
};
let client = remote_client(&remote, None)
.await
.map_err(map_remote_err)?;
let client = remote_client(&remote, None).await.map_err(map_remote_err)?;
let api_res = client
.get(&format!("api2/json/admin/datastore/{}/groups", store), None)
.await
@ -402,13 +433,8 @@ pub async fn scan_remote_groups(name: String, store: String) -> Result<Vec<Group
}
#[sortable]
const DATASTORE_SCAN_SUBDIRS: SubdirMap = &[
(
"groups",
&Router::new()
.get(&API_METHOD_SCAN_REMOTE_GROUPS)
),
];
const DATASTORE_SCAN_SUBDIRS: SubdirMap =
&[("groups", &Router::new().get(&API_METHOD_SCAN_REMOTE_GROUPS))];
const DATASTORE_SCAN_ROUTER: Router = Router::new()
.get(&list_subdirs_api_method!(DATASTORE_SCAN_SUBDIRS))

View File

@ -1,15 +1,15 @@
use anyhow::{bail, Error};
use serde_json::Value;
use ::serde::{Deserialize, Serialize};
use anyhow::{bail, Error};
use hex::FromHex;
use serde_json::Value;
use proxmox_router::{http_bail, Router, RpcEnvironment, Permission};
use proxmox_router::{http_bail, Permission, Router, RpcEnvironment};
use proxmox_schema::{api, param_bail};
use pbs_api_types::{
Authid, SyncJobConfig, SyncJobConfigUpdater, JOB_ID_SCHEMA, PROXMOX_CONFIG_DIGEST_SCHEMA,
PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_MODIFY, PRIV_DATASTORE_PRUNE,
PRIV_REMOTE_AUDIT, PRIV_REMOTE_READ,
Authid, SyncJobConfig, SyncJobConfigUpdater, JOB_ID_SCHEMA, PRIV_DATASTORE_AUDIT,
PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_MODIFY, PRIV_DATASTORE_PRUNE, PRIV_REMOTE_AUDIT,
PRIV_REMOTE_READ, PROXMOX_CONFIG_DIGEST_SCHEMA,
};
use pbs_config::sync;
@ -49,10 +49,8 @@ pub fn check_sync_job_modify_access(
let correct_owner = match job.owner {
Some(ref owner) => {
owner == auth_id
|| (owner.is_token()
&& !auth_id.is_token()
&& owner.user() == auth_id.user())
},
|| (owner.is_token() && !auth_id.is_token() && owner.user() == auth_id.user())
}
// default sync owner
None => auth_id == Authid::root_auth_id(),
};
@ -98,7 +96,7 @@ pub fn list_sync_jobs(
.into_iter()
.filter(|sync_job| check_sync_job_read_access(&user_info, &auth_id, sync_job))
.collect();
Ok(list)
Ok(list)
}
#[api(
@ -181,7 +179,7 @@ pub fn read_sync_job(
#[api()]
#[derive(Serialize, Deserialize)]
#[serde(rename_all="kebab-case")]
#[serde(rename_all = "kebab-case")]
#[allow(non_camel_case_types)]
/// Deletable property name
pub enum DeletableProperty {
@ -258,18 +256,36 @@ pub fn update_sync_job(
let mut data: SyncJobConfig = config.lookup("sync", &id)?;
if let Some(delete) = delete {
if let Some(delete) = delete {
for delete_prop in delete {
match delete_prop {
DeletableProperty::owner => { data.owner = None; },
DeletableProperty::comment => { data.comment = None; },
DeletableProperty::schedule => { data.schedule = None; },
DeletableProperty::remove_vanished => { data.remove_vanished = None; },
DeletableProperty::group_filter => { data.group_filter = None; },
DeletableProperty::rate_in => { data.limit.rate_in = None; },
DeletableProperty::rate_out => { data.limit.rate_out = None; },
DeletableProperty::burst_in => { data.limit.burst_in = None; },
DeletableProperty::burst_out => { data.limit.burst_out = None; },
DeletableProperty::owner => {
data.owner = None;
}
DeletableProperty::comment => {
data.comment = None;
}
DeletableProperty::schedule => {
data.schedule = None;
}
DeletableProperty::remove_vanished => {
data.remove_vanished = None;
}
DeletableProperty::group_filter => {
data.group_filter = None;
}
DeletableProperty::rate_in => {
data.limit.rate_in = None;
}
DeletableProperty::rate_out => {
data.limit.rate_out = None;
}
DeletableProperty::burst_in => {
data.limit.burst_in = None;
}
DeletableProperty::burst_out => {
data.limit.burst_out = None;
}
}
}
}
@ -283,11 +299,21 @@ pub fn update_sync_job(
}
}
if let Some(store) = update.store { data.store = store; }
if let Some(remote) = update.remote { data.remote = remote; }
if let Some(remote_store) = update.remote_store { data.remote_store = remote_store; }
if let Some(owner) = update.owner { data.owner = Some(owner); }
if let Some(group_filter) = update.group_filter { data.group_filter = Some(group_filter); }
if let Some(store) = update.store {
data.store = store;
}
if let Some(remote) = update.remote {
data.remote = remote;
}
if let Some(remote_store) = update.remote_store {
data.remote_store = remote_store;
}
if let Some(owner) = update.owner {
data.owner = Some(owner);
}
if let Some(group_filter) = update.group_filter {
data.group_filter = Some(group_filter);
}
if update.limit.rate_in.is_some() {
data.limit.rate_in = update.limit.rate_in;
@ -306,8 +332,12 @@ pub fn update_sync_job(
}
let schedule_changed = data.schedule != update.schedule;
if update.schedule.is_some() { data.schedule = update.schedule; }
if update.remove_vanished.is_some() { data.remove_vanished = update.remove_vanished; }
if update.schedule.is_some() {
data.schedule = update.schedule;
}
if update.remove_vanished.is_some() {
data.remove_vanished = update.remove_vanished;
}
if !check_sync_job_modify_access(&user_info, &auth_id, &data) {
bail!("permission check failed");
@ -366,8 +396,10 @@ pub fn delete_sync_job(
bail!("permission check failed");
}
config.sections.remove(&id);
},
Err(_) => { http_bail!(NOT_FOUND, "job '{}' does not exist.", id) },
}
Err(_) => {
http_bail!(NOT_FOUND, "job '{}' does not exist.", id)
}
};
sync::save_config(&config)?;
@ -387,25 +419,30 @@ pub const ROUTER: Router = Router::new()
.post(&API_METHOD_CREATE_SYNC_JOB)
.match_all("id", &ITEM_ROUTER);
#[test]
fn sync_job_access_test() -> Result<(), Error> {
let (user_cfg, _) = pbs_config::user::test_cfg_from_str(r###"
let (user_cfg, _) = pbs_config::user::test_cfg_from_str(
r###"
user: noperm@pbs
user: read@pbs
user: write@pbs
"###).expect("test user.cfg is not parsable");
let acl_tree = pbs_config::acl::AclTree::from_raw(r###"
"###,
)
.expect("test user.cfg is not parsable");
let acl_tree = pbs_config::acl::AclTree::from_raw(
r###"
acl:1:/datastore/localstore1:read@pbs,write@pbs:DatastoreAudit
acl:1:/datastore/localstore1:write@pbs:DatastoreBackup
acl:1:/datastore/localstore2:write@pbs:DatastorePowerUser
acl:1:/datastore/localstore3:write@pbs:DatastoreAdmin
acl:1:/remote/remote1:read@pbs,write@pbs:RemoteAudit
acl:1:/remote/remote1/remotestore1:write@pbs:RemoteSyncOperator
"###).expect("test acl.cfg is not parsable");
"###,
)
.expect("test acl.cfg is not parsable");
let user_info = CachedUserInfo::test_new(user_cfg, acl_tree);
@ -429,28 +466,52 @@ acl:1:/remote/remote1/remotestore1:write@pbs:RemoteSyncOperator
};
// should work without ACLs
assert_eq!(check_sync_job_read_access(&user_info, root_auth_id, &job), true);
assert_eq!(check_sync_job_modify_access(&user_info, root_auth_id, &job), true);
assert_eq!(
check_sync_job_read_access(&user_info, root_auth_id, &job),
true
);
assert_eq!(
check_sync_job_modify_access(&user_info, root_auth_id, &job),
true
);
// user without permissions must fail
assert_eq!(check_sync_job_read_access(&user_info, &no_perm_auth_id, &job), false);
assert_eq!(check_sync_job_modify_access(&user_info, &no_perm_auth_id, &job), false);
assert_eq!(
check_sync_job_read_access(&user_info, &no_perm_auth_id, &job),
false
);
assert_eq!(
check_sync_job_modify_access(&user_info, &no_perm_auth_id, &job),
false
);
// reading without proper read permissions on either remote or local must fail
assert_eq!(check_sync_job_read_access(&user_info, &read_auth_id, &job), false);
assert_eq!(
check_sync_job_read_access(&user_info, &read_auth_id, &job),
false
);
// reading without proper read permissions on local end must fail
job.remote = "remote1".to_string();
assert_eq!(check_sync_job_read_access(&user_info, &read_auth_id, &job), false);
assert_eq!(
check_sync_job_read_access(&user_info, &read_auth_id, &job),
false
);
// reading without proper read permissions on remote end must fail
job.remote = "remote0".to_string();
job.store = "localstore1".to_string();
assert_eq!(check_sync_job_read_access(&user_info, &read_auth_id, &job), false);
assert_eq!(
check_sync_job_read_access(&user_info, &read_auth_id, &job),
false
);
// writing without proper write permissions on either end must fail
job.store = "localstore0".to_string();
assert_eq!(check_sync_job_modify_access(&user_info, &write_auth_id, &job), false);
assert_eq!(
check_sync_job_modify_access(&user_info, &write_auth_id, &job),
false
);
// writing without proper write permissions on local end must fail
job.remote = "remote1".to_string();
@ -458,46 +519,85 @@ acl:1:/remote/remote1/remotestore1:write@pbs:RemoteSyncOperator
// writing without proper write permissions on remote end must fail
job.remote = "remote0".to_string();
job.store = "localstore1".to_string();
assert_eq!(check_sync_job_modify_access(&user_info, &write_auth_id, &job), false);
assert_eq!(
check_sync_job_modify_access(&user_info, &write_auth_id, &job),
false
);
// reset remote to one where users have access
job.remote = "remote1".to_string();
// user with read permission can only read, but not modify/run
assert_eq!(check_sync_job_read_access(&user_info, &read_auth_id, &job), true);
assert_eq!(
check_sync_job_read_access(&user_info, &read_auth_id, &job),
true
);
job.owner = Some(read_auth_id.clone());
assert_eq!(check_sync_job_modify_access(&user_info, &read_auth_id, &job), false);
assert_eq!(
check_sync_job_modify_access(&user_info, &read_auth_id, &job),
false
);
job.owner = None;
assert_eq!(check_sync_job_modify_access(&user_info, &read_auth_id, &job), false);
assert_eq!(
check_sync_job_modify_access(&user_info, &read_auth_id, &job),
false
);
job.owner = Some(write_auth_id.clone());
assert_eq!(check_sync_job_modify_access(&user_info, &read_auth_id, &job), false);
assert_eq!(
check_sync_job_modify_access(&user_info, &read_auth_id, &job),
false
);
// user with simple write permission can modify/run
assert_eq!(check_sync_job_read_access(&user_info, &write_auth_id, &job), true);
assert_eq!(check_sync_job_modify_access(&user_info, &write_auth_id, &job), true);
assert_eq!(
check_sync_job_read_access(&user_info, &write_auth_id, &job),
true
);
assert_eq!(
check_sync_job_modify_access(&user_info, &write_auth_id, &job),
true
);
// but can't modify/run with deletion
job.remove_vanished = Some(true);
assert_eq!(check_sync_job_modify_access(&user_info, &write_auth_id, &job), false);
assert_eq!(
check_sync_job_modify_access(&user_info, &write_auth_id, &job),
false
);
// unless they have Datastore.Prune as well
job.store = "localstore2".to_string();
assert_eq!(check_sync_job_modify_access(&user_info, &write_auth_id, &job), true);
assert_eq!(
check_sync_job_modify_access(&user_info, &write_auth_id, &job),
true
);
// changing owner is not possible
job.owner = Some(read_auth_id.clone());
assert_eq!(check_sync_job_modify_access(&user_info, &write_auth_id, &job), false);
assert_eq!(
check_sync_job_modify_access(&user_info, &write_auth_id, &job),
false
);
// also not to the default 'root@pam'
job.owner = None;
assert_eq!(check_sync_job_modify_access(&user_info, &write_auth_id, &job), false);
assert_eq!(
check_sync_job_modify_access(&user_info, &write_auth_id, &job),
false
);
// unless they have Datastore.Modify as well
job.store = "localstore3".to_string();
job.owner = Some(read_auth_id);
assert_eq!(check_sync_job_modify_access(&user_info, &write_auth_id, &job), true);
assert_eq!(
check_sync_job_modify_access(&user_info, &write_auth_id, &job),
true
);
job.owner = None;
assert_eq!(check_sync_job_modify_access(&user_info, &write_auth_id, &job), true);
assert_eq!(
check_sync_job_modify_access(&user_info, &write_auth_id, &job),
true
);
Ok(())
}

View File

@ -1,15 +1,14 @@
use anyhow::Error;
use serde_json::Value;
use ::serde::{Deserialize, Serialize};
use anyhow::Error;
use hex::FromHex;
use serde_json::Value;
use proxmox_router::{http_bail, ApiMethod, Router, RpcEnvironment, Permission};
use proxmox_router::{http_bail, ApiMethod, Permission, Router, RpcEnvironment};
use proxmox_schema::{api, param_bail};
use pbs_api_types::{
TrafficControlRule, TrafficControlRuleUpdater,
TrafficControlRule, TrafficControlRuleUpdater, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY,
PROXMOX_CONFIG_DIGEST_SCHEMA, TRAFFIC_CONTROL_ID_SCHEMA,
PRIV_SYS_AUDIT, PRIV_SYS_MODIFY,
};
#[api(
@ -56,13 +55,16 @@ pub fn list_traffic_controls(
)]
/// Create new traffic control rule.
pub fn create_traffic_control(config: TrafficControlRule) -> Result<(), Error> {
let _lock = pbs_config::traffic_control::lock_config()?;
let (mut section_config, _digest) = pbs_config::traffic_control::config()?;
if section_config.sections.get(&config.name).is_some() {
param_bail!("name", "traffic control rule '{}' already exists.", config.name);
param_bail!(
"name",
"traffic control rule '{}' already exists.",
config.name
);
}
section_config.set_data(&config.name, "rule", &config)?;
@ -154,7 +156,6 @@ pub fn update_traffic_control(
delete: Option<Vec<DeletableProperty>>,
digest: Option<String>,
) -> Result<(), Error> {
let _lock = pbs_config::traffic_control::lock_config()?;
let (mut config, expected_digest) = pbs_config::traffic_control::config()?;
@ -169,12 +170,24 @@ pub fn update_traffic_control(
if let Some(delete) = delete {
for delete_prop in delete {
match delete_prop {
DeletableProperty::rate_in => { data.limit.rate_in = None; },
DeletableProperty::rate_out => { data.limit.rate_out = None; },
DeletableProperty::burst_in => { data.limit.burst_in = None; },
DeletableProperty::burst_out => { data.limit.burst_out = None; },
DeletableProperty::comment => { data.comment = None; },
DeletableProperty::timeframe => { data.timeframe = None; },
DeletableProperty::rate_in => {
data.limit.rate_in = None;
}
DeletableProperty::rate_out => {
data.limit.rate_out = None;
}
DeletableProperty::burst_in => {
data.limit.burst_in = None;
}
DeletableProperty::burst_out => {
data.limit.burst_out = None;
}
DeletableProperty::comment => {
data.comment = None;
}
DeletableProperty::timeframe => {
data.timeframe = None;
}
}
}
}
@ -204,8 +217,12 @@ pub fn update_traffic_control(
data.limit.burst_out = update.limit.burst_out;
}
if let Some(network) = update.network { data.network = network; }
if update.timeframe.is_some() { data.timeframe = update.timeframe; }
if let Some(network) = update.network {
data.network = network;
}
if update.timeframe.is_some() {
data.timeframe = update.timeframe;
}
config.set_data(&name, "rule", &data)?;
@ -233,7 +250,6 @@ pub fn update_traffic_control(
)]
/// Remove a traffic control rule from the configuration file.
pub fn delete_traffic_control(name: String, digest: Option<String>) -> Result<(), Error> {
let _lock = pbs_config::traffic_control::lock_config()?;
let (mut config, expected_digest) = pbs_config::traffic_control::config()?;
@ -244,7 +260,9 @@ pub fn delete_traffic_control(name: String, digest: Option<String>) -> Result<()
}
match config.sections.get(&name) {
Some(_) => { config.sections.remove(&name); },
Some(_) => {
config.sections.remove(&name);
}
None => http_bail!(NOT_FOUND, "traffic control rule '{}' does not exist.", name),
}
@ -253,7 +271,6 @@ pub fn delete_traffic_control(name: String, digest: Option<String>) -> Result<()
Ok(())
}
const ITEM_ROUTER: Router = Router::new()
.get(&API_METHOD_READ_TRAFFIC_CONTROL)
.put(&API_METHOD_UPDATE_TRAFFIC_CONTROL)

View File

@ -1,14 +1,14 @@
use anyhow::Error;
use serde_json::Value;
use ::serde::{Deserialize, Serialize};
use anyhow::Error;
use hex::FromHex;
use serde_json::Value;
use proxmox_router::{http_bail, Router, RpcEnvironment, Permission};
use proxmox_router::{http_bail, Permission, Router, RpcEnvironment};
use proxmox_schema::{api, param_bail};
use pbs_api_types::{
Authid, VerificationJobConfig, VerificationJobConfigUpdater, JOB_ID_SCHEMA,
PROXMOX_CONFIG_DIGEST_SCHEMA, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_VERIFY,
PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_VERIFY, PROXMOX_CONFIG_DIGEST_SCHEMA,
};
use pbs_config::verify;
@ -42,19 +42,20 @@ pub fn list_verification_jobs(
let list = config.convert_to_typed_array("verification")?;
let list = list.into_iter()
let list = list
.into_iter()
.filter(|job: &VerificationJobConfig| {
let privs = user_info.lookup_privs(&auth_id, &["datastore", &job.store]);
privs & required_privs != 00
}).collect();
})
.collect();
rpcenv["digest"] = hex::encode(&digest).into();
Ok(list)
}
#[api(
protected: true,
input: {
@ -73,12 +74,17 @@ pub fn list_verification_jobs(
/// Create a new verification job.
pub fn create_verification_job(
config: VerificationJobConfig,
rpcenv: &mut dyn RpcEnvironment
rpcenv: &mut dyn RpcEnvironment,
) -> Result<(), Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let user_info = CachedUserInfo::new()?;
user_info.check_privs(&auth_id, &["datastore", &config.store], PRIV_DATASTORE_VERIFY, false)?;
user_info.check_privs(
&auth_id,
&["datastore", &config.store],
PRIV_DATASTORE_VERIFY,
false,
)?;
let _lock = verify::lock_config()?;
@ -124,7 +130,12 @@ pub fn read_verification_job(
let verification_job: VerificationJobConfig = config.lookup("verification", &id)?;
let required_privs = PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_VERIFY;
user_info.check_privs(&auth_id, &["datastore", &verification_job.store], required_privs, true)?;
user_info.check_privs(
&auth_id,
&["datastore", &verification_job.store],
required_privs,
true,
)?;
rpcenv["digest"] = hex::encode(&digest).into();
@ -133,7 +144,7 @@ pub fn read_verification_job(
#[api()]
#[derive(Serialize, Deserialize)]
#[serde(rename_all="kebab-case")]
#[serde(rename_all = "kebab-case")]
/// Deletable property name
pub enum DeletableProperty {
/// Delete the ignore verified property.
@ -143,7 +154,7 @@ pub enum DeletableProperty {
/// Delete the job schedule.
Schedule,
/// Delete outdated after property.
OutdatedAfter
OutdatedAfter,
}
#[api(
@ -201,15 +212,28 @@ pub fn update_verification_job(
let mut data: VerificationJobConfig = config.lookup("verification", &id)?;
// check existing store
user_info.check_privs(&auth_id, &["datastore", &data.store], PRIV_DATASTORE_VERIFY, true)?;
user_info.check_privs(
&auth_id,
&["datastore", &data.store],
PRIV_DATASTORE_VERIFY,
true,
)?;
if let Some(delete) = delete {
for delete_prop in delete {
match delete_prop {
DeletableProperty::IgnoreVerified => { data.ignore_verified = None; },
DeletableProperty::OutdatedAfter => { data.outdated_after = None; },
DeletableProperty::Comment => { data.comment = None; },
DeletableProperty::Schedule => { data.schedule = None; },
DeletableProperty::IgnoreVerified => {
data.ignore_verified = None;
}
DeletableProperty::OutdatedAfter => {
data.outdated_after = None;
}
DeletableProperty::Comment => {
data.comment = None;
}
DeletableProperty::Schedule => {
data.schedule = None;
}
}
}
}
@ -225,15 +249,25 @@ pub fn update_verification_job(
if let Some(store) = update.store {
// check new store
user_info.check_privs(&auth_id, &["datastore", &store], PRIV_DATASTORE_VERIFY, true)?;
user_info.check_privs(
&auth_id,
&["datastore", &store],
PRIV_DATASTORE_VERIFY,
true,
)?;
data.store = store;
}
if update.ignore_verified.is_some() { data.ignore_verified = update.ignore_verified; }
if update.outdated_after.is_some() { data.outdated_after = update.outdated_after; }
if update.ignore_verified.is_some() {
data.ignore_verified = update.ignore_verified;
}
if update.outdated_after.is_some() {
data.outdated_after = update.outdated_after;
}
let schedule_changed = data.schedule != update.schedule;
if update.schedule.is_some() { data.schedule = update.schedule; }
if update.schedule.is_some() {
data.schedule = update.schedule;
}
config.set_data(&id, "verification", &data)?;
@ -278,7 +312,12 @@ pub fn delete_verification_job(
let (mut config, expected_digest) = verify::config()?;
let job: VerificationJobConfig = config.lookup("verification", &id)?;
user_info.check_privs(&auth_id, &["datastore", &job.store], PRIV_DATASTORE_VERIFY, true)?;
user_info.check_privs(
&auth_id,
&["datastore", &job.store],
PRIV_DATASTORE_VERIFY,
true,
)?;
if let Some(ref digest) = digest {
let digest = <[u8; 32]>::from_hex(digest)?;
@ -286,7 +325,9 @@ pub fn delete_verification_job(
}
match config.sections.get(&id) {
Some(_) => { config.sections.remove(&id); },
Some(_) => {
config.sections.remove(&id);
}
None => http_bail!(NOT_FOUND, "job '{}' does not exist.", id),
}

View File

@ -2,7 +2,7 @@ use std::path::PathBuf;
use anyhow::Error;
use futures::stream::TryStreamExt;
use hyper::{Body, Response, StatusCode, header};
use hyper::{header, Body, Response, StatusCode};
use proxmox_router::http_bail;

View File

@ -4,15 +4,15 @@ pub mod access;
pub mod admin;
pub mod backup;
pub mod config;
pub mod helpers;
pub mod node;
pub mod reader;
pub mod status;
pub mod types;
pub mod version;
pub mod ping;
pub mod pull;
pub mod reader;
pub mod status;
pub mod tape;
pub mod helpers;
pub mod types;
pub mod version;
use proxmox_router::{list_subdirs_api_method, Router, SubdirMap};

View File

@ -1,12 +1,12 @@
use anyhow::{Error, bail, format_err};
use anyhow::{bail, format_err, Error};
use serde_json::{json, Value};
use std::collections::HashMap;
use proxmox_sys::fs::{replace_file, CreateOptions};
use proxmox_router::{
list_subdirs_api_method, RpcEnvironment, RpcEnvironmentType, Permission, Router, SubdirMap
list_subdirs_api_method, Permission, Router, RpcEnvironment, RpcEnvironmentType, SubdirMap,
};
use proxmox_schema::api;
use proxmox_sys::fs::{replace_file, CreateOptions};
use proxmox_apt::repositories::{
APTRepositoryFile, APTRepositoryFileError, APTRepositoryHandle, APTRepositoryInfo,
@ -15,17 +15,13 @@ use proxmox_apt::repositories::{
use proxmox_http::ProxyConfig;
use pbs_api_types::{
APTUpdateInfo, NODE_SCHEMA, PROXMOX_CONFIG_DIGEST_SCHEMA, UPID_SCHEMA,
PRIV_SYS_AUDIT, PRIV_SYS_MODIFY,
APTUpdateInfo, NODE_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY, PROXMOX_CONFIG_DIGEST_SCHEMA,
UPID_SCHEMA,
};
use crate::config::node;
use crate::tools::{apt, pbs_simple_http, subscription};
use proxmox_rest_server::WorkerTask;
use crate::tools::{
apt,
pbs_simple_http,
subscription,
};
#[api(
input: {
@ -49,7 +45,6 @@ use crate::tools::{
)]
/// List available APT updates
fn apt_update_available(_param: Value) -> Result<Value, Error> {
if let Ok(false) = apt::pkg_cache_expired() {
if let Ok(Some(cache)) = apt::read_pkg_state() {
return Ok(json!(cache.package_status));
@ -62,7 +57,6 @@ fn apt_update_available(_param: Value) -> Result<Value, Error> {
}
pub fn update_apt_proxy_config(proxy_config: Option<&ProxyConfig>) -> Result<(), Error> {
const PROXY_CFG_FN: &str = "/etc/apt/apt.conf.d/76pveproxy"; // use same file as PVE
if let Some(proxy_config) = proxy_config {
@ -90,7 +84,9 @@ fn read_and_update_proxy_config() -> Result<Option<ProxyConfig>, Error> {
}
fn do_apt_update(worker: &WorkerTask, quiet: bool) -> Result<(), Error> {
if !quiet { worker.log_message("starting apt-get update") }
if !quiet {
worker.log_message("starting apt-get update")
}
read_and_update_proxy_config()?;
@ -98,7 +94,8 @@ fn do_apt_update(worker: &WorkerTask, quiet: bool) -> Result<(), Error> {
command.arg("update");
// apt "errors" quite easily, and run_command is a bit rigid, so handle this inline for now.
let output = command.output()
let output = command
.output()
.map_err(|err| format_err!("failed to execute {:?} - {}", command, err))?;
if !quiet {
@ -109,7 +106,13 @@ fn do_apt_update(worker: &WorkerTask, quiet: bool) -> Result<(), Error> {
if !output.status.success() {
if output.status.code().is_some() {
let msg = String::from_utf8(output.stderr)
.map(|m| if m.is_empty() { String::from("no error message") } else { m })
.map(|m| {
if m.is_empty() {
String::from("no error message")
} else {
m
}
})
.unwrap_or_else(|_| String::from("non utf8 error message (suppressed)"));
worker.log_warning(msg);
} else {
@ -154,7 +157,6 @@ pub fn apt_update_database(
quiet: bool,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<String, Error> {
let auth_id = rpcenv.get_auth_id().unwrap();
let to_stdout = rpcenv.env_type() == RpcEnvironmentType::CLI;
@ -176,7 +178,7 @@ pub fn apt_update_database(
if notified_version != pkg.version {
to_notify.push(pkg);
}
},
}
None => to_notify.push(pkg),
}
}
@ -220,19 +222,17 @@ pub fn apt_update_database(
},
)]
/// Retrieve the changelog of the specified package.
fn apt_get_changelog(
param: Value,
) -> Result<Value, Error> {
fn apt_get_changelog(param: Value) -> Result<Value, Error> {
let name = pbs_tools::json::required_string_param(&param, "name")?.to_owned();
let version = param["version"].as_str();
let pkg_info = apt::list_installed_apt_packages(|data| {
match version {
let pkg_info = apt::list_installed_apt_packages(
|data| match version {
Some(version) => version == data.active_version,
None => data.active_version == data.candidate_version
}
}, Some(&name));
None => data.active_version == data.candidate_version,
},
Some(&name),
);
if pkg_info.is_empty() {
bail!("Package '{}' not found", name);
@ -245,33 +245,47 @@ fn apt_get_changelog(
// FIXME: use 'apt-get changelog' for proxmox packages as well, once repo supports it
if changelog_url.starts_with("http://download.proxmox.com/") {
let changelog = proxmox_async::runtime::block_on(client.get_string(changelog_url, None))
.map_err(|err| format_err!("Error downloading changelog from '{}': {}", changelog_url, err))?;
.map_err(|err| {
format_err!(
"Error downloading changelog from '{}': {}",
changelog_url,
err
)
})?;
Ok(json!(changelog))
} else if changelog_url.starts_with("https://enterprise.proxmox.com/") {
let sub = match subscription::read_subscription()? {
Some(sub) => sub,
None => bail!("cannot retrieve changelog from enterprise repo: no subscription info found")
None => {
bail!("cannot retrieve changelog from enterprise repo: no subscription info found")
}
};
let (key, id) = match sub.key {
Some(key) => {
match sub.serverid {
Some(id) => (key, id),
None =>
bail!("cannot retrieve changelog from enterprise repo: no server id found")
}
Some(key) => match sub.serverid {
Some(id) => (key, id),
None => bail!("cannot retrieve changelog from enterprise repo: no server id found"),
},
None => bail!("cannot retrieve changelog from enterprise repo: no subscription key found")
None => {
bail!("cannot retrieve changelog from enterprise repo: no subscription key found")
}
};
let mut auth_header = HashMap::new();
auth_header.insert("Authorization".to_owned(),
format!("Basic {}", base64::encode(format!("{}:{}", key, id))));
auth_header.insert(
"Authorization".to_owned(),
format!("Basic {}", base64::encode(format!("{}:{}", key, id))),
);
let changelog = proxmox_async::runtime::block_on(client.get_string(changelog_url, Some(&auth_header)))
.map_err(|err| format_err!("Error downloading changelog from '{}': {}", changelog_url, err))?;
let changelog =
proxmox_async::runtime::block_on(client.get_string(changelog_url, Some(&auth_header)))
.map_err(|err| {
format_err!(
"Error downloading changelog from '{}': {}",
changelog_url,
err
)
})?;
Ok(json!(changelog))
} else {
let mut command = std::process::Command::new("apt-get");
command.arg("changelog");
@ -348,23 +362,35 @@ pub fn get_versions() -> Result<Vec<APTUpdateInfo>, Error> {
"running kernel: {}",
nix::sys::utsname::uname().release().to_owned()
);
if let Some(proxmox_backup) = pbs_packages.iter().find(|pkg| pkg.package == "proxmox-backup") {
if let Some(proxmox_backup) = pbs_packages
.iter()
.find(|pkg| pkg.package == "proxmox-backup")
{
let mut proxmox_backup = proxmox_backup.clone();
proxmox_backup.extra_info = Some(running_kernel);
packages.push(proxmox_backup);
} else {
packages.push(unknown_package("proxmox-backup".into(), Some(running_kernel)));
packages.push(unknown_package(
"proxmox-backup".into(),
Some(running_kernel),
));
}
let version = pbs_buildcfg::PROXMOX_PKG_VERSION;
let release = pbs_buildcfg::PROXMOX_PKG_RELEASE;
let daemon_version_info = Some(format!("running version: {}.{}", version, release));
if let Some(pkg) = pbs_packages.iter().find(|pkg| pkg.package == "proxmox-backup-server") {
if let Some(pkg) = pbs_packages
.iter()
.find(|pkg| pkg.package == "proxmox-backup-server")
{
let mut pkg = pkg.clone();
pkg.extra_info = daemon_version_info;
packages.push(pkg);
} else {
packages.push(unknown_package("proxmox-backup".into(), daemon_version_info));
packages.push(unknown_package(
"proxmox-backup".into(),
daemon_version_info,
));
}
let mut kernel_pkgs: Vec<APTUpdateInfo> = pbs_packages
@ -609,15 +635,22 @@ pub fn change_repository(
}
const SUBDIRS: SubdirMap = &[
("changelog", &Router::new().get(&API_METHOD_APT_GET_CHANGELOG)),
("repositories", &Router::new()
.get(&API_METHOD_GET_REPOSITORIES)
.post(&API_METHOD_CHANGE_REPOSITORY)
.put(&API_METHOD_ADD_REPOSITORY)
(
"changelog",
&Router::new().get(&API_METHOD_APT_GET_CHANGELOG),
),
("update", &Router::new()
.get(&API_METHOD_APT_UPDATE_AVAILABLE)
.post(&API_METHOD_APT_UPDATE_DATABASE)
(
"repositories",
&Router::new()
.get(&API_METHOD_GET_REPOSITORIES)
.post(&API_METHOD_CHANGE_REPOSITORY)
.put(&API_METHOD_ADD_REPOSITORY),
),
(
"update",
&Router::new()
.get(&API_METHOD_APT_UPDATE_AVAILABLE)
.post(&API_METHOD_APT_UPDATE_DATABASE),
),
("versions", &Router::new().get(&API_METHOD_GET_VERSIONS)),
];

View File

@ -7,9 +7,9 @@ use openssl::pkey::PKey;
use openssl::x509::X509;
use serde::{Deserialize, Serialize};
use proxmox_router::list_subdirs_api_method;
use proxmox_router::SubdirMap;
use proxmox_router::{Permission, Router, RpcEnvironment};
use proxmox_router::list_subdirs_api_method;
use proxmox_schema::api;
use proxmox_sys::{task_log, task_warn};
@ -305,7 +305,10 @@ async fn order_certificate(
};
if domains.is_empty() {
task_log!(worker, "No domains configured to be ordered from an ACME server.");
task_log!(
worker,
"No domains configured to be ordered from an ACME server."
);
return Ok(None);
}
@ -363,7 +366,9 @@ async fn order_certificate(
task_warn!(
worker,
"Failed to teardown plugin '{}' for domain '{}' - {}",
plugin_id, domain, err
plugin_id,
domain,
err
);
}
@ -453,7 +458,10 @@ async fn request_validation(
let auth = acme.get_authorization(auth_url).await?;
match auth.status {
Status::Pending => {
task_log!(worker, "Status is still 'pending', trying again in 10 seconds");
task_log!(
worker,
"Status is still 'pending', trying again in 10 seconds"
);
tokio::time::sleep(Duration::from_secs(10)).await;
}
Status::Valid => return Ok(()),
@ -574,7 +582,10 @@ pub fn revoke_acme_cert(rpcenv: &mut dyn RpcEnvironment) -> Result<String, Error
let mut acme = node_config.acme_client().await?;
task_log!(worker, "Revoking old certificate");
acme.revoke_certificate(cert_pem.as_bytes(), None).await?;
task_log!(worker, "Deleting certificate and regenerating a self-signed one");
task_log!(
worker,
"Deleting certificate and regenerating a self-signed one"
);
delete_custom_certificate().await?;
Ok(())
},

View File

@ -1,5 +1,5 @@
use anyhow::Error;
use ::serde::{Deserialize, Serialize};
use anyhow::Error;
use hex::FromHex;
use proxmox_router::{Permission, Router, RpcEnvironment};
@ -36,7 +36,7 @@ pub fn get_node_config(mut rpcenv: &mut dyn RpcEnvironment) -> Result<NodeConfig
#[api()]
#[derive(Serialize, Deserialize)]
#[serde(rename_all="kebab-case")]
#[serde(rename_all = "kebab-case")]
#[allow(non_camel_case_types)]
/// Deletable property name
pub enum DeletableProperty {
@ -57,10 +57,10 @@ pub enum DeletableProperty {
/// Delete the email-from property.
email_from,
/// Delete the ciphers-tls-1.3 property.
#[serde(rename="ciphers-tls-1.3")]
#[serde(rename = "ciphers-tls-1.3")]
ciphers_tls_1_3,
/// Delete the ciphers-tls-1.2 property.
#[serde(rename="ciphers-tls-1.2")]
#[serde(rename = "ciphers-tls-1.2")]
ciphers_tls_1_2,
/// Delete the default-lang property.
default_lang,
@ -117,36 +117,88 @@ pub fn update_node_config(
if let Some(delete) = delete {
for delete_prop in delete {
match delete_prop {
DeletableProperty::acme => { config.acme = None; },
DeletableProperty::acmedomain0 => { config.acmedomain0 = None; },
DeletableProperty::acmedomain1 => { config.acmedomain1 = None; },
DeletableProperty::acmedomain2 => { config.acmedomain2 = None; },
DeletableProperty::acmedomain3 => { config.acmedomain3 = None; },
DeletableProperty::acmedomain4 => { config.acmedomain4 = None; },
DeletableProperty::http_proxy => { config.http_proxy = None; },
DeletableProperty::email_from => { config.email_from = None; },
DeletableProperty::ciphers_tls_1_3 => { config.ciphers_tls_1_3 = None; },
DeletableProperty::ciphers_tls_1_2 => { config.ciphers_tls_1_2 = None; },
DeletableProperty::default_lang => { config.default_lang = None; },
DeletableProperty::description => { config.description = None; },
DeletableProperty::task_log_max_days => { config.task_log_max_days = None; },
DeletableProperty::acme => {
config.acme = None;
}
DeletableProperty::acmedomain0 => {
config.acmedomain0 = None;
}
DeletableProperty::acmedomain1 => {
config.acmedomain1 = None;
}
DeletableProperty::acmedomain2 => {
config.acmedomain2 = None;
}
DeletableProperty::acmedomain3 => {
config.acmedomain3 = None;
}
DeletableProperty::acmedomain4 => {
config.acmedomain4 = None;
}
DeletableProperty::http_proxy => {
config.http_proxy = None;
}
DeletableProperty::email_from => {
config.email_from = None;
}
DeletableProperty::ciphers_tls_1_3 => {
config.ciphers_tls_1_3 = None;
}
DeletableProperty::ciphers_tls_1_2 => {
config.ciphers_tls_1_2 = None;
}
DeletableProperty::default_lang => {
config.default_lang = None;
}
DeletableProperty::description => {
config.description = None;
}
DeletableProperty::task_log_max_days => {
config.task_log_max_days = None;
}
}
}
}
if update.acme.is_some() { config.acme = update.acme; }
if update.acmedomain0.is_some() { config.acmedomain0 = update.acmedomain0; }
if update.acmedomain1.is_some() { config.acmedomain1 = update.acmedomain1; }
if update.acmedomain2.is_some() { config.acmedomain2 = update.acmedomain2; }
if update.acmedomain3.is_some() { config.acmedomain3 = update.acmedomain3; }
if update.acmedomain4.is_some() { config.acmedomain4 = update.acmedomain4; }
if update.http_proxy.is_some() { config.http_proxy = update.http_proxy; }
if update.email_from.is_some() { config.email_from = update.email_from; }
if update.ciphers_tls_1_3.is_some() { config.ciphers_tls_1_3 = update.ciphers_tls_1_3; }
if update.ciphers_tls_1_2.is_some() { config.ciphers_tls_1_2 = update.ciphers_tls_1_2; }
if update.default_lang.is_some() { config.default_lang = update.default_lang; }
if update.description.is_some() { config.description = update.description; }
if update.task_log_max_days.is_some() { config.task_log_max_days = update.task_log_max_days; }
if update.acme.is_some() {
config.acme = update.acme;
}
if update.acmedomain0.is_some() {
config.acmedomain0 = update.acmedomain0;
}
if update.acmedomain1.is_some() {
config.acmedomain1 = update.acmedomain1;
}
if update.acmedomain2.is_some() {
config.acmedomain2 = update.acmedomain2;
}
if update.acmedomain3.is_some() {
config.acmedomain3 = update.acmedomain3;
}
if update.acmedomain4.is_some() {
config.acmedomain4 = update.acmedomain4;
}
if update.http_proxy.is_some() {
config.http_proxy = update.http_proxy;
}
if update.email_from.is_some() {
config.email_from = update.email_from;
}
if update.ciphers_tls_1_3.is_some() {
config.ciphers_tls_1_3 = update.ciphers_tls_1_3;
}
if update.ciphers_tls_1_2.is_some() {
config.ciphers_tls_1_2 = update.ciphers_tls_1_2;
}
if update.default_lang.is_some() {
config.default_lang = update.default_lang;
}
if update.description.is_some() {
config.description = update.description;
}
if update.task_log_max_days.is_some() {
config.task_log_max_days = update.task_log_max_days;
}
crate::config::node::save_config(&config)?;

View File

@ -1,20 +1,20 @@
use ::serde::{Deserialize, Serialize};
use anyhow::{bail, Error};
use serde_json::json;
use ::serde::{Deserialize, Serialize};
use proxmox_router::{Router, RpcEnvironment, RpcEnvironmentType, Permission};
use proxmox_router::{Permission, Router, RpcEnvironment, RpcEnvironmentType};
use proxmox_schema::api;
use proxmox_section_config::SectionConfigData;
use proxmox_sys::task_log;
use pbs_api_types::{
DataStoreConfig, NODE_SCHEMA, BLOCKDEVICE_NAME_SCHEMA,
DATASTORE_SCHEMA, UPID_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY,
DataStoreConfig, BLOCKDEVICE_NAME_SCHEMA, DATASTORE_SCHEMA, NODE_SCHEMA, PRIV_SYS_AUDIT,
PRIV_SYS_MODIFY, UPID_SCHEMA,
};
use crate::tools::disks::{
DiskManage, FileSystemType, DiskUsageType,
create_file_system, create_single_linux_partition, get_fs_uuid, get_disk_usage_info,
create_file_system, create_single_linux_partition, get_disk_usage_info, get_fs_uuid,
DiskManage, DiskUsageType, FileSystemType,
};
use crate::tools::systemd::{self, types::*};
@ -31,7 +31,7 @@ const BASE_MOUNT_DIR: &str = "/mnt/datastore/";
},
)]
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all="kebab-case")]
#[serde(rename_all = "kebab-case")]
/// Datastore mount info.
pub struct DatastoreMountInfo {
/// The path of the mount unit.
@ -69,8 +69,7 @@ pub struct DatastoreMountInfo {
},
)]
/// List systemd datastore mount units.
pub fn list_datastore_mounts() -> Result<Vec<DatastoreMountInfo>, Error> {
pub fn list_datastore_mounts() -> Result<Vec<DatastoreMountInfo>, Error> {
lazy_static::lazy_static! {
static ref MOUNT_NAME_REGEX: regex::Regex = regex::Regex::new(r"^mnt-datastore-(.+)\.mount$").unwrap();
}
@ -144,7 +143,6 @@ pub fn create_datastore_disk(
filesystem: Option<FileSystemType>,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<String, Error> {
let to_stdout = rpcenv.env_type() == RpcEnvironmentType::CLI;
let auth_id = rpcenv.get_auth_id().unwrap();
@ -161,15 +159,18 @@ pub fn create_datastore_disk(
let default_path = std::path::PathBuf::from(&mount_point);
match std::fs::metadata(&default_path) {
Err(_) => {}, // path does not exist
Err(_) => {} // path does not exist
Ok(_) => {
bail!("path {:?} already exists", default_path);
}
}
let upid_str = WorkerTask::new_thread(
"dircreate", Some(name.clone()), auth_id, to_stdout, move |worker|
{
"dircreate",
Some(name.clone()),
auth_id,
to_stdout,
move |worker| {
task_log!(worker, "create datastore '{}' on disk {}", name, disk);
let add_datastore = add_datastore.unwrap_or(false);
@ -185,7 +186,8 @@ pub fn create_datastore_disk(
let uuid = get_fs_uuid(&partition)?;
let uuid_path = format!("/dev/disk/by-uuid/{}", uuid);
let mount_unit_name = create_datastore_mount_unit(&name, &mount_point, filesystem, &uuid_path)?;
let mount_unit_name =
create_datastore_mount_unit(&name, &mount_point, filesystem, &uuid_path)?;
crate::tools::systemd::reload_daemon()?;
crate::tools::systemd::enable_unit(&mount_unit_name)?;
@ -202,11 +204,17 @@ pub fn create_datastore_disk(
bail!("datastore '{}' already exists.", datastore.name);
}
crate::api2::config::datastore::do_create_datastore(lock, config, datastore, Some(&worker))?;
crate::api2::config::datastore::do_create_datastore(
lock,
config,
datastore,
Some(&worker),
)?;
}
Ok(())
})?;
},
)?;
Ok(upid_str)
}
@ -229,17 +237,19 @@ pub fn create_datastore_disk(
)]
/// Remove a Filesystem mounted under '/mnt/datastore/<name>'.".
pub fn delete_datastore_disk(name: String) -> Result<(), Error> {
let path = format!("{}{}", BASE_MOUNT_DIR, name);
// path of datastore cannot be changed
let (config, _) = pbs_config::datastore::config()?;
let datastores: Vec<DataStoreConfig> = config.convert_to_typed_array("datastore")?;
let conflicting_datastore: Option<DataStoreConfig> = datastores.into_iter()
.find(|ds| ds.path == path);
let conflicting_datastore: Option<DataStoreConfig> =
datastores.into_iter().find(|ds| ds.path == path);
if let Some(conflicting_datastore) = conflicting_datastore {
bail!("Can't remove '{}' since it's required by datastore '{}'",
conflicting_datastore.path, conflicting_datastore.name);
bail!(
"Can't remove '{}' since it's required by datastore '{}'",
conflicting_datastore.path,
conflicting_datastore.name
);
}
// disable systemd mount-unit
@ -262,33 +272,33 @@ pub fn delete_datastore_disk(name: String) -> Result<(), Error> {
until the next reboot or until unmounted manually!",
path
),
Ok(_) => Ok(())
Ok(_) => Ok(()),
}
}
const ITEM_ROUTER: Router = Router::new()
.delete(&API_METHOD_DELETE_DATASTORE_DISK);
const ITEM_ROUTER: Router = Router::new().delete(&API_METHOD_DELETE_DATASTORE_DISK);
pub const ROUTER: Router = Router::new()
.get(&API_METHOD_LIST_DATASTORE_MOUNTS)
.post(&API_METHOD_CREATE_DATASTORE_DISK)
.match_all("name", &ITEM_ROUTER);
fn create_datastore_mount_unit(
datastore_name: &str,
mount_point: &str,
fs_type: FileSystemType,
what: &str,
) -> Result<String, Error> {
let mut mount_unit_name = proxmox_sys::systemd::escape_unit(mount_point, true);
mount_unit_name.push_str(".mount");
let mount_unit_path = format!("/etc/systemd/system/{}", mount_unit_name);
let unit = SystemdUnitSection {
Description: format!("Mount datatstore '{}' under '{}'", datastore_name, mount_point),
Description: format!(
"Mount datatstore '{}' under '{}'",
datastore_name, mount_point
),
..Default::default()
};

View File

@ -1,25 +1,22 @@
use anyhow::{bail, Error};
use serde_json::{json, Value};
use proxmox_router::{Router, RpcEnvironment, RpcEnvironmentType, Permission};
use proxmox_router::{Permission, Router, RpcEnvironment, RpcEnvironmentType};
use proxmox_schema::api;
use proxmox_sys::task_log;
use pbs_api_types::{
ZpoolListItem, ZfsRaidLevel, ZfsCompressionType, DataStoreConfig,
NODE_SCHEMA, ZPOOL_NAME_SCHEMA, DATASTORE_SCHEMA, DISK_ARRAY_SCHEMA,
DISK_LIST_SCHEMA, ZFS_ASHIFT_SCHEMA, UPID_SCHEMA,
PRIV_SYS_AUDIT, PRIV_SYS_MODIFY,
DataStoreConfig, ZfsCompressionType, ZfsRaidLevel, ZpoolListItem, DATASTORE_SCHEMA,
DISK_ARRAY_SCHEMA, DISK_LIST_SCHEMA, NODE_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY, UPID_SCHEMA,
ZFS_ASHIFT_SCHEMA, ZPOOL_NAME_SCHEMA,
};
use crate::tools::disks::{
zpool_list, zpool_status, parse_zpool_status_config_tree, vdev_list_to_tree,
DiskUsageType,
parse_zpool_status_config_tree, vdev_list_to_tree, zpool_list, zpool_status, DiskUsageType,
};
use proxmox_rest_server::WorkerTask;
#[api(
protected: true,
input: {
@ -42,7 +39,6 @@ use proxmox_rest_server::WorkerTask;
)]
/// List zfs pools.
pub fn list_zpools() -> Result<Vec<ZpoolListItem>, Error> {
let data = zpool_list(None, false)?;
let mut list = Vec::new();
@ -87,15 +83,12 @@ pub fn list_zpools() -> Result<Vec<ZpoolListItem>, Error> {
},
)]
/// Get zpool status details.
pub fn zpool_details(
name: String,
) -> Result<Value, Error> {
pub fn zpool_details(name: String) -> Result<Value, Error> {
let key_value_list = zpool_status(&name)?;
let config = match key_value_list.iter().find(|(k, _)| k == "config") {
Some((_, v)) => v,
None => bail!("got zpool status without config key"),
None => bail!("got zpool status without config key"),
};
let vdev_list = parse_zpool_status_config_tree(config)?;
@ -107,11 +100,12 @@ pub fn zpool_details(
}
}
tree["name"] = tree.as_object_mut().unwrap()
tree["name"] = tree
.as_object_mut()
.unwrap()
.remove("pool")
.unwrap_or_else(|| name.into());
Ok(tree)
}
@ -163,7 +157,6 @@ pub fn create_zpool(
add_datastore: Option<bool>,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<String, Error> {
let to_stdout = rpcenv.env_type() == RpcEnvironmentType::CLI;
let auth_id = rpcenv.get_auth_id().unwrap();
@ -174,8 +167,12 @@ pub fn create_zpool(
let devices_text = devices.clone();
let devices = DISK_ARRAY_SCHEMA.parse_property_string(&devices)?;
let devices: Vec<String> = devices.as_array().unwrap().iter()
.map(|v| v.as_str().unwrap().to_string()).collect();
let devices: Vec<String> = devices
.as_array()
.unwrap()
.iter()
.map(|v| v.as_str().unwrap().to_string())
.collect();
let disk_map = crate::tools::disks::get_disks(None, true)?;
for disk in devices.iter() {
@ -220,20 +217,35 @@ pub fn create_zpool(
let default_path = std::path::PathBuf::from(&mount_point);
match std::fs::metadata(&default_path) {
Err(_) => {}, // path does not exist
Err(_) => {} // path does not exist
Ok(_) => {
bail!("path {:?} already exists", default_path);
}
}
let upid_str = WorkerTask::new_thread(
"zfscreate", Some(name.clone()), auth_id, to_stdout, move |worker|
{
task_log!(worker, "create {:?} zpool '{}' on devices '{}'", raidlevel, name, devices_text);
let upid_str = WorkerTask::new_thread(
"zfscreate",
Some(name.clone()),
auth_id,
to_stdout,
move |worker| {
task_log!(
worker,
"create {:?} zpool '{}' on devices '{}'",
raidlevel,
name,
devices_text
);
let mut command = std::process::Command::new("zpool");
command.args(&["create", "-o", &format!("ashift={}", ashift), "-m", &mount_point, &name]);
command.args(&[
"create",
"-o",
&format!("ashift={}", ashift),
"-m",
&mount_point,
&name,
]);
match raidlevel {
ZfsRaidLevel::Single => {
@ -244,10 +256,10 @@ pub fn create_zpool(
command.args(devices);
}
ZfsRaidLevel::Raid10 => {
devices.chunks(2).for_each(|pair| {
command.arg("mirror");
command.args(pair);
});
devices.chunks(2).for_each(|pair| {
command.arg("mirror");
command.args(pair);
});
}
ZfsRaidLevel::RaidZ => {
command.arg("raidz");
@ -269,7 +281,10 @@ pub fn create_zpool(
task_log!(worker, "{}", output);
if std::path::Path::new("/lib/systemd/system/zfs-import@.service").exists() {
let import_unit = format!("zfs-import@{}.service", proxmox_sys::systemd::escape_unit(&name, false));
let import_unit = format!(
"zfs-import@{}.service",
proxmox_sys::systemd::escape_unit(&name, false)
);
crate::tools::systemd::enable_unit(&import_unit)?;
}
@ -294,17 +309,22 @@ pub fn create_zpool(
bail!("datastore '{}' already exists.", datastore.name);
}
crate::api2::config::datastore::do_create_datastore(lock, config, datastore, Some(&worker))?;
crate::api2::config::datastore::do_create_datastore(
lock,
config,
datastore,
Some(&worker),
)?;
}
Ok(())
})?;
},
)?;
Ok(upid_str)
}
pub const POOL_ROUTER: Router = Router::new()
.get(&API_METHOD_ZPOOL_DETAILS);
pub const POOL_ROUTER: Router = Router::new().get(&API_METHOD_ZPOOL_DETAILS);
pub const ROUTER: Router = Router::new()
.get(&API_METHOD_LIST_ZPOOLS)

View File

@ -1,21 +1,21 @@
use std::sync::{Arc, Mutex};
use anyhow::{Error};
use ::serde::{Deserialize, Serialize};
use anyhow::Error;
use lazy_static::lazy_static;
use openssl::sha;
use regex::Regex;
use serde_json::{json, Value};
use ::serde::{Deserialize, Serialize};
use proxmox_router::{ApiMethod, Router, RpcEnvironment, Permission};
use pbs_api_types::{IPRE, IPV4OCTET, IPV4RE, IPV6H16, IPV6LS32, IPV6RE};
use proxmox_router::{ApiMethod, Permission, Router, RpcEnvironment};
use proxmox_schema::api;
use proxmox_sys::fs::{file_get_contents, replace_file, CreateOptions};
use pbs_api_types::{IPRE, IPV4RE, IPV6RE, IPV4OCTET, IPV6H16, IPV6LS32};
use pbs_api_types::{
PROXMOX_CONFIG_DIGEST_SCHEMA, FIRST_DNS_SERVER_SCHEMA, SECOND_DNS_SERVER_SCHEMA,
THIRD_DNS_SERVER_SCHEMA, NODE_SCHEMA, SEARCH_DOMAIN_SCHEMA,
PRIV_SYS_AUDIT, PRIV_SYS_MODIFY,
FIRST_DNS_SERVER_SCHEMA, NODE_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY,
PROXMOX_CONFIG_DIGEST_SCHEMA, SEARCH_DOMAIN_SCHEMA, SECOND_DNS_SERVER_SCHEMA,
THIRD_DNS_SERVER_SCHEMA,
};
static RESOLV_CONF_FN: &str = "/etc/resolv.conf";
@ -34,7 +34,6 @@ pub enum DeletableProperty {
}
pub fn read_etc_resolv_conf() -> Result<Value, Error> {
let mut result = json!({});
let mut nscount = 0;
@ -47,24 +46,27 @@ pub fn read_etc_resolv_conf() -> Result<Value, Error> {
lazy_static! {
static ref DOMAIN_REGEX: Regex = Regex::new(r"^\s*(?:search|domain)\s+(\S+)\s*").unwrap();
static ref SERVER_REGEX: Regex = Regex::new(
concat!(r"^\s*nameserver\s+(", IPRE!(), r")\s*")).unwrap();
static ref SERVER_REGEX: Regex =
Regex::new(concat!(r"^\s*nameserver\s+(", IPRE!(), r")\s*")).unwrap();
}
let mut options = String::new();
for line in data.lines() {
if let Some(caps) = DOMAIN_REGEX.captures(line) {
result["search"] = Value::from(&caps[1]);
} else if let Some(caps) = SERVER_REGEX.captures(line) {
nscount += 1;
if nscount > 3 { continue };
if nscount > 3 {
continue;
};
let nameserver = &caps[1];
let id = format!("dns{}", nscount);
result[id] = Value::from(nameserver);
} else {
if !options.is_empty() { options.push('\n'); }
if !options.is_empty() {
options.push('\n');
}
options.push_str(line);
}
}
@ -127,7 +129,6 @@ pub fn update_dns(
delete: Option<Vec<DeletableProperty>>,
digest: Option<String>,
) -> Result<Value, Error> {
lazy_static! {
static ref MUTEX: Arc<Mutex<()>> = Arc::new(Mutex::new(()));
}
@ -145,17 +146,31 @@ pub fn update_dns(
for delete_prop in delete {
let config = config.as_object_mut().unwrap();
match delete_prop {
DeletableProperty::dns1 => { config.remove("dns1"); },
DeletableProperty::dns2 => { config.remove("dns2"); },
DeletableProperty::dns3 => { config.remove("dns3"); },
DeletableProperty::dns1 => {
config.remove("dns1");
}
DeletableProperty::dns2 => {
config.remove("dns2");
}
DeletableProperty::dns3 => {
config.remove("dns3");
}
}
}
}
if let Some(search) = search { config["search"] = search.into(); }
if let Some(dns1) = dns1 { config["dns1"] = dns1.into(); }
if let Some(dns2) = dns2 { config["dns2"] = dns2.into(); }
if let Some(dns3) = dns3 { config["dns3"] = dns3.into(); }
if let Some(search) = search {
config["search"] = search.into();
}
if let Some(dns1) = dns1 {
config["dns1"] = dns1.into();
}
if let Some(dns2) = dns2 {
config["dns2"] = dns2.into();
}
if let Some(dns3) = dns3 {
config["dns3"] = dns3.into();
}
let mut data = String::new();
@ -219,7 +234,6 @@ pub fn get_dns(
_info: &ApiMethod,
_rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
read_etc_resolv_conf()
}

View File

@ -1,10 +1,10 @@
use std::process::{Command, Stdio};
use anyhow::{Error};
use anyhow::Error;
use serde_json::{json, Value};
use std::io::{BufRead,BufReader};
use std::io::{BufRead, BufReader};
use proxmox_router::{ApiMethod, Router, RpcEnvironment, Permission};
use proxmox_router::{ApiMethod, Permission, Router, RpcEnvironment};
use proxmox_schema::api;
use pbs_api_types::{NODE_SCHEMA, PRIV_SYS_AUDIT};
@ -69,7 +69,6 @@ fn get_journal(
_info: &ApiMethod,
_rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
let mut args = vec![];
if let Some(lastentries) = lastentries {
@ -127,5 +126,4 @@ fn get_journal(
Ok(json!(lines))
}
pub const ROUTER: Router = Router::new()
.get(&API_METHOD_GET_JOURNAL);
pub const ROUTER: Router = Router::new().get(&API_METHOD_GET_JOURNAL);

View File

@ -12,23 +12,23 @@ use hyper::Request;
use serde_json::{json, Value};
use tokio::io::{AsyncBufReadExt, BufReader};
use proxmox_sys::sortable;
use proxmox_sys::fd::fd_change_cloexec;
use proxmox_sys::sortable;
use proxmox_http::websocket::WebSocket;
use proxmox_router::list_subdirs_api_method;
use proxmox_router::{
ApiHandler, ApiMethod, ApiResponseFuture, Permission, RpcEnvironment, Router, SubdirMap,
ApiHandler, ApiMethod, ApiResponseFuture, Permission, Router, RpcEnvironment, SubdirMap,
};
use proxmox_schema::*;
use proxmox_router::list_subdirs_api_method;
use proxmox_http::websocket::WebSocket;
use proxmox_rest_server::WorkerTask;
use pbs_api_types::{Authid, NODE_SCHEMA, PRIV_SYS_CONSOLE};
use pbs_tools::ticket::{self, Empty, Ticket};
use crate::tools;
use crate::auth_helpers::private_auth_key;
use crate::tools;
pub mod apt;
pub mod certificates;
@ -303,7 +303,7 @@ fn upgrade_to_websocket(
.map_err(Error::from)
.await
{
Ok(upgraded) => upgraded,
Ok(upgraded) => upgraded,
_ => bail!("error"),
};

View File

@ -1,16 +1,16 @@
use anyhow::{Error, bail};
use serde::{Deserialize, Serialize};
use serde_json::{Value, to_value};
use anyhow::{bail, Error};
use hex::FromHex;
use serde::{Deserialize, Serialize};
use serde_json::{to_value, Value};
use proxmox_router::{ApiMethod, Router, RpcEnvironment, Permission};
use proxmox_router::{ApiMethod, Permission, Router, RpcEnvironment};
use proxmox_schema::api;
use pbs_api_types::{
Authid, Interface, NetworkInterfaceType, LinuxBondMode, NetworkConfigMethod, BondXmitHashPolicy,
Authid, BondXmitHashPolicy, Interface, LinuxBondMode, NetworkConfigMethod,
NetworkInterfaceType, CIDR_V4_SCHEMA, CIDR_V6_SCHEMA, IP_V4_SCHEMA, IP_V6_SCHEMA,
NETWORK_INTERFACE_ARRAY_SCHEMA, NETWORK_INTERFACE_LIST_SCHEMA, NETWORK_INTERFACE_NAME_SCHEMA,
CIDR_V4_SCHEMA, CIDR_V6_SCHEMA, IP_V4_SCHEMA, IP_V6_SCHEMA, PROXMOX_CONFIG_DIGEST_SCHEMA,
NODE_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY,
NODE_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY, PROXMOX_CONFIG_DIGEST_SCHEMA,
};
use pbs_config::network::{self, NetworkConfig};
@ -18,41 +18,57 @@ use proxmox_rest_server::WorkerTask;
fn split_interface_list(list: &str) -> Result<Vec<String>, Error> {
let value = NETWORK_INTERFACE_ARRAY_SCHEMA.parse_property_string(list)?;
Ok(value.as_array().unwrap().iter().map(|v| v.as_str().unwrap().to_string()).collect())
Ok(value
.as_array()
.unwrap()
.iter()
.map(|v| v.as_str().unwrap().to_string())
.collect())
}
fn check_duplicate_gateway_v4(config: &NetworkConfig, iface: &str) -> Result<(), Error> {
let current_gateway_v4 = config.interfaces.iter()
let current_gateway_v4 = config
.interfaces
.iter()
.find(|(_, interface)| interface.gateway.is_some())
.map(|(name, _)| name.to_string());
if let Some(current_gateway_v4) = current_gateway_v4 {
if current_gateway_v4 != iface {
bail!("Default IPv4 gateway already exists on interface '{}'", current_gateway_v4);
bail!(
"Default IPv4 gateway already exists on interface '{}'",
current_gateway_v4
);
}
}
Ok(())
}
fn check_duplicate_gateway_v6(config: &NetworkConfig, iface: &str) -> Result<(), Error> {
let current_gateway_v6 = config.interfaces.iter()
let current_gateway_v6 = config
.interfaces
.iter()
.find(|(_, interface)| interface.gateway6.is_some())
.map(|(name, _)| name.to_string());
if let Some(current_gateway_v6) = current_gateway_v6 {
if current_gateway_v6 != iface {
bail!("Default IPv6 gateway already exists on interface '{}'", current_gateway_v6);
bail!(
"Default IPv6 gateway already exists on interface '{}'",
current_gateway_v6
);
}
}
Ok(())
}
fn set_bridge_ports(iface: &mut Interface, ports: Vec<String>) -> Result<(), Error> {
if iface.interface_type != NetworkInterfaceType::Bridge {
bail!("interface '{}' is no bridge (type is {:?})", iface.name, iface.interface_type);
bail!(
"interface '{}' is no bridge (type is {:?})",
iface.name,
iface.interface_type
);
}
iface.bridge_ports = Some(ports);
Ok(())
@ -60,7 +76,11 @@ fn set_bridge_ports(iface: &mut Interface, ports: Vec<String>) -> Result<(), Err
fn set_bond_slaves(iface: &mut Interface, slaves: Vec<String>) -> Result<(), Error> {
if iface.interface_type != NetworkInterfaceType::Bond {
bail!("interface '{}' is no bond (type is {:?})", iface.name, iface.interface_type);
bail!(
"interface '{}' is no bond (type is {:?})",
iface.name,
iface.interface_type
);
}
iface.slaves = Some(slaves);
Ok(())
@ -91,14 +111,15 @@ pub fn list_network_devices(
_info: &ApiMethod,
mut rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
let (config, digest) = network::config()?;
let digest = hex::encode(&digest);
let mut list = Vec::new();
for (iface, interface) in config.interfaces.iter() {
if iface == "lo" { continue; } // do not list lo
if iface == "lo" {
continue;
} // do not list lo
let mut item: Value = to_value(interface)?;
item["digest"] = digest.clone().into();
item["iface"] = iface.to_string().into();
@ -131,7 +152,6 @@ pub fn list_network_devices(
)]
/// Read a network interface configuration.
pub fn read_interface(iface: String) -> Result<Value, Error> {
let (config, digest) = network::config()?;
let interface = config.lookup(&iface)?;
@ -142,7 +162,6 @@ pub fn read_interface(iface: String) -> Result<Value, Error> {
Ok(data)
}
#[api(
protected: true,
input: {
@ -256,7 +275,6 @@ pub fn create_interface(
slaves: Option<String>,
param: Value,
) -> Result<(), Error> {
let interface_type = pbs_tools::json::required_string_param(&param, "type")?;
let interface_type: NetworkInterfaceType = serde_json::from_value(interface_type.into())?;
@ -271,35 +289,55 @@ pub fn create_interface(
let mut interface = Interface::new(iface.clone());
interface.interface_type = interface_type;
if let Some(autostart) = autostart { interface.autostart = autostart; }
if method.is_some() { interface.method = method; }
if method6.is_some() { interface.method6 = method6; }
if mtu.is_some() { interface.mtu = mtu; }
if comments.is_some() { interface.comments = comments; }
if comments6.is_some() { interface.comments6 = comments6; }
if let Some(autostart) = autostart {
interface.autostart = autostart;
}
if method.is_some() {
interface.method = method;
}
if method6.is_some() {
interface.method6 = method6;
}
if mtu.is_some() {
interface.mtu = mtu;
}
if comments.is_some() {
interface.comments = comments;
}
if comments6.is_some() {
interface.comments6 = comments6;
}
if let Some(cidr) = cidr {
let (_, _, is_v6) = network::parse_cidr(&cidr)?;
if is_v6 { bail!("invalid address type (expected IPv4, got IPv6)"); }
if is_v6 {
bail!("invalid address type (expected IPv4, got IPv6)");
}
interface.cidr = Some(cidr);
}
if let Some(cidr6) = cidr6 {
let (_, _, is_v6) = network::parse_cidr(&cidr6)?;
if !is_v6 { bail!("invalid address type (expected IPv6, got IPv4)"); }
if !is_v6 {
bail!("invalid address type (expected IPv6, got IPv4)");
}
interface.cidr6 = Some(cidr6);
}
if let Some(gateway) = gateway {
let is_v6 = gateway.contains(':');
if is_v6 { bail!("invalid address type (expected IPv4, got IPv6)"); }
if is_v6 {
bail!("invalid address type (expected IPv4, got IPv6)");
}
check_duplicate_gateway_v4(&config, &iface)?;
interface.gateway = Some(gateway);
}
if let Some(gateway6) = gateway6 {
let is_v6 = gateway6.contains(':');
if !is_v6 { bail!("invalid address type (expected IPv6, got IPv4)"); }
if !is_v6 {
bail!("invalid address type (expected IPv6, got IPv4)");
}
check_duplicate_gateway_v6(&config, &iface)?;
interface.gateway6 = Some(gateway6);
}
@ -310,7 +348,9 @@ pub fn create_interface(
let ports = split_interface_list(&ports)?;
set_bridge_ports(&mut interface, ports)?;
}
if bridge_vlan_aware.is_some() { interface.bridge_vlan_aware = bridge_vlan_aware; }
if bridge_vlan_aware.is_some() {
interface.bridge_vlan_aware = bridge_vlan_aware;
}
}
NetworkInterfaceType::Bond => {
if let Some(mode) = bond_mode {
@ -322,9 +362,7 @@ pub fn create_interface(
interface.bond_primary = bond_primary;
}
if bond_xmit_hash_policy.is_some() {
if mode != LinuxBondMode::ieee802_3ad &&
mode != LinuxBondMode::balance_xor
{
if mode != LinuxBondMode::ieee802_3ad && mode != LinuxBondMode::balance_xor {
bail!("bond_xmit_hash_policy is only valid with LACP(802.3ad) or balance-xor mode");
}
interface.bond_xmit_hash_policy = bond_xmit_hash_policy;
@ -335,7 +373,10 @@ pub fn create_interface(
set_bond_slaves(&mut interface, slaves)?;
}
}
_ => bail!("creating network interface type '{:?}' is not supported", interface_type),
_ => bail!(
"creating network interface type '{:?}' is not supported",
interface_type
),
}
if interface.cidr.is_some() || interface.gateway.is_some() {
@ -395,7 +436,6 @@ pub enum DeletableProperty {
bond_xmit_hash_policy,
}
#[api(
protected: true,
input: {
@ -523,7 +563,6 @@ pub fn update_interface(
digest: Option<String>,
param: Value,
) -> Result<(), Error> {
let _lock = network::lock_config()?;
let (mut config, expected_digest) = network::config()?;
@ -533,49 +572,95 @@ pub fn update_interface(
crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
}
if gateway.is_some() { check_duplicate_gateway_v4(&config, &iface)?; }
if gateway6.is_some() { check_duplicate_gateway_v6(&config, &iface)?; }
if gateway.is_some() {
check_duplicate_gateway_v4(&config, &iface)?;
}
if gateway6.is_some() {
check_duplicate_gateway_v6(&config, &iface)?;
}
let interface = config.lookup_mut(&iface)?;
if let Some(interface_type) = param.get("type") {
let interface_type = NetworkInterfaceType::deserialize(interface_type)?;
if interface_type != interface.interface_type {
bail!("got unexpected interface type ({:?} != {:?})", interface_type, interface.interface_type);
if interface_type != interface.interface_type {
bail!(
"got unexpected interface type ({:?} != {:?})",
interface_type,
interface.interface_type
);
}
}
if let Some(delete) = delete {
for delete_prop in delete {
match delete_prop {
DeletableProperty::cidr => { interface.cidr = None; },
DeletableProperty::cidr6 => { interface.cidr6 = None; },
DeletableProperty::gateway => { interface.gateway = None; },
DeletableProperty::gateway6 => { interface.gateway6 = None; },
DeletableProperty::method => { interface.method = None; },
DeletableProperty::method6 => { interface.method6 = None; },
DeletableProperty::comments => { interface.comments = None; },
DeletableProperty::comments6 => { interface.comments6 = None; },
DeletableProperty::mtu => { interface.mtu = None; },
DeletableProperty::autostart => { interface.autostart = false; },
DeletableProperty::bridge_ports => { set_bridge_ports(interface, Vec::new())?; }
DeletableProperty::bridge_vlan_aware => { interface.bridge_vlan_aware = None; }
DeletableProperty::slaves => { set_bond_slaves(interface, Vec::new())?; }
DeletableProperty::bond_primary => { interface.bond_primary = None; }
DeletableProperty::bond_xmit_hash_policy => { interface.bond_xmit_hash_policy = None }
DeletableProperty::cidr => {
interface.cidr = None;
}
DeletableProperty::cidr6 => {
interface.cidr6 = None;
}
DeletableProperty::gateway => {
interface.gateway = None;
}
DeletableProperty::gateway6 => {
interface.gateway6 = None;
}
DeletableProperty::method => {
interface.method = None;
}
DeletableProperty::method6 => {
interface.method6 = None;
}
DeletableProperty::comments => {
interface.comments = None;
}
DeletableProperty::comments6 => {
interface.comments6 = None;
}
DeletableProperty::mtu => {
interface.mtu = None;
}
DeletableProperty::autostart => {
interface.autostart = false;
}
DeletableProperty::bridge_ports => {
set_bridge_ports(interface, Vec::new())?;
}
DeletableProperty::bridge_vlan_aware => {
interface.bridge_vlan_aware = None;
}
DeletableProperty::slaves => {
set_bond_slaves(interface, Vec::new())?;
}
DeletableProperty::bond_primary => {
interface.bond_primary = None;
}
DeletableProperty::bond_xmit_hash_policy => interface.bond_xmit_hash_policy = None,
}
}
}
if let Some(autostart) = autostart { interface.autostart = autostart; }
if method.is_some() { interface.method = method; }
if method6.is_some() { interface.method6 = method6; }
if mtu.is_some() { interface.mtu = mtu; }
if let Some(autostart) = autostart {
interface.autostart = autostart;
}
if method.is_some() {
interface.method = method;
}
if method6.is_some() {
interface.method6 = method6;
}
if mtu.is_some() {
interface.mtu = mtu;
}
if let Some(ports) = bridge_ports {
let ports = split_interface_list(&ports)?;
set_bridge_ports(interface, ports)?;
}
if bridge_vlan_aware.is_some() { interface.bridge_vlan_aware = bridge_vlan_aware; }
if bridge_vlan_aware.is_some() {
interface.bridge_vlan_aware = bridge_vlan_aware;
}
if let Some(slaves) = slaves {
let slaves = split_interface_list(&slaves)?;
set_bond_slaves(interface, slaves)?;
@ -589,9 +674,7 @@ pub fn update_interface(
interface.bond_primary = bond_primary;
}
if bond_xmit_hash_policy.is_some() {
if mode != LinuxBondMode::ieee802_3ad &&
mode != LinuxBondMode::balance_xor
{
if mode != LinuxBondMode::ieee802_3ad && mode != LinuxBondMode::balance_xor {
bail!("bond_xmit_hash_policy is only valid with LACP(802.3ad) or balance-xor mode");
}
interface.bond_xmit_hash_policy = bond_xmit_hash_policy;
@ -600,30 +683,42 @@ pub fn update_interface(
if let Some(cidr) = cidr {
let (_, _, is_v6) = network::parse_cidr(&cidr)?;
if is_v6 { bail!("invalid address type (expected IPv4, got IPv6)"); }
if is_v6 {
bail!("invalid address type (expected IPv4, got IPv6)");
}
interface.cidr = Some(cidr);
}
if let Some(cidr6) = cidr6 {
let (_, _, is_v6) = network::parse_cidr(&cidr6)?;
if !is_v6 { bail!("invalid address type (expected IPv6, got IPv4)"); }
if !is_v6 {
bail!("invalid address type (expected IPv6, got IPv4)");
}
interface.cidr6 = Some(cidr6);
}
if let Some(gateway) = gateway {
let is_v6 = gateway.contains(':');
if is_v6 { bail!("invalid address type (expected IPv4, got IPv6)"); }
if is_v6 {
bail!("invalid address type (expected IPv4, got IPv6)");
}
interface.gateway = Some(gateway);
}
if let Some(gateway6) = gateway6 {
let is_v6 = gateway6.contains(':');
if !is_v6 { bail!("invalid address type (expected IPv6, got IPv4)"); }
if !is_v6 {
bail!("invalid address type (expected IPv6, got IPv4)");
}
interface.gateway6 = Some(gateway6);
}
if comments.is_some() { interface.comments = comments; }
if comments6.is_some() { interface.comments6 = comments6; }
if comments.is_some() {
interface.comments = comments;
}
if comments6.is_some() {
interface.comments6 = comments6;
}
if interface.cidr.is_some() || interface.gateway.is_some() {
interface.method = Some(NetworkConfigMethod::Static);
@ -696,21 +791,26 @@ pub fn delete_interface(iface: String, digest: Option<String>) -> Result<(), Err
},
)]
/// Reload network configuration (requires ifupdown2).
pub async fn reload_network_config(
rpcenv: &mut dyn RpcEnvironment,
) -> Result<String, Error> {
pub async fn reload_network_config(rpcenv: &mut dyn RpcEnvironment) -> Result<String, Error> {
network::assert_ifupdown2_installed()?;
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let upid_str = WorkerTask::spawn("srvreload", Some(String::from("networking")), auth_id.to_string(), true, |_worker| async {
let upid_str = WorkerTask::spawn(
"srvreload",
Some(String::from("networking")),
auth_id.to_string(),
true,
|_worker| async {
let _ = std::fs::rename(
network::NETWORK_INTERFACES_NEW_FILENAME,
network::NETWORK_INTERFACES_FILENAME,
);
let _ = std::fs::rename(network::NETWORK_INTERFACES_NEW_FILENAME, network::NETWORK_INTERFACES_FILENAME);
network::network_reload()?;
Ok(())
})?;
network::network_reload()?;
Ok(())
},
)?;
Ok(upid_str)
}
@ -730,7 +830,6 @@ pub async fn reload_network_config(
)]
/// Revert network configuration (rm /etc/network/interfaces.new).
pub fn revert_network_config() -> Result<(), Error> {
let _ = std::fs::remove_file(network::NETWORK_INTERFACES_NEW_FILENAME);
Ok(())

View File

@ -33,5 +33,4 @@ fn get_report(
Ok(json!(generate_report()))
}
pub const ROUTER: Router = Router::new()
.get(&API_METHOD_GET_REPORT);
pub const ROUTER: Router = Router::new().get(&API_METHOD_GET_REPORT);

View File

@ -1,13 +1,11 @@
use anyhow::{bail, Error};
use serde_json::{Value, json};
use serde_json::{json, Value};
use std::collections::BTreeMap;
use proxmox_router::{Permission, Router};
use proxmox_schema::api;
use pbs_api_types::{
NODE_SCHEMA, RRDMode, RRDTimeFrame, PRIV_SYS_AUDIT,
};
use pbs_api_types::{RRDMode, RRDTimeFrame, NODE_SCHEMA, PRIV_SYS_AUDIT};
use crate::rrd_cache::extract_rrd_data;
@ -17,7 +15,6 @@ pub fn create_value_from_rrd(
timeframe: RRDTimeFrame,
mode: RRDMode,
) -> Result<Value, Error> {
let mut result: Vec<Value> = Vec::new();
let mut timemap = BTreeMap::new();
@ -30,9 +27,13 @@ pub fn create_value_from_rrd(
None => continue,
};
if let Some(expected_resolution) = last_resolution {
if let Some(expected_resolution) = last_resolution {
if reso != expected_resolution {
bail!("got unexpected RRD resolution ({} != {})", reso, expected_resolution);
bail!(
"got unexpected RRD resolution ({} != {})",
reso,
expected_resolution
);
}
} else {
last_resolution = Some(reso);
@ -75,29 +76,30 @@ pub fn create_value_from_rrd(
},
)]
/// Read node stats
fn get_node_stats(
timeframe: RRDTimeFrame,
cf: RRDMode,
_param: Value,
) -> Result<Value, Error> {
fn get_node_stats(timeframe: RRDTimeFrame, cf: RRDMode, _param: Value) -> Result<Value, Error> {
create_value_from_rrd(
"host",
&[
"cpu", "iowait",
"memtotal", "memused",
"swaptotal", "swapused",
"netin", "netout",
"cpu",
"iowait",
"memtotal",
"memused",
"swaptotal",
"swapused",
"netin",
"netout",
"loadavg",
"total", "used",
"read_ios", "read_bytes",
"write_ios", "write_bytes",
"total",
"used",
"read_ios",
"read_bytes",
"write_ios",
"write_bytes",
"io_ticks",
],
],
timeframe,
cf,
)
}
pub const ROUTER: Router = Router::new()
.get(&API_METHOD_GET_NODE_STATS);
pub const ROUTER: Router = Router::new().get(&API_METHOD_GET_NODE_STATS);

View File

@ -3,11 +3,11 @@ use std::process::{Command, Stdio};
use anyhow::{bail, Error};
use serde_json::{json, Value};
use proxmox_sys::sortable;
use proxmox_router::{list_subdirs_api_method, Router, Permission, RpcEnvironment, SubdirMap};
use proxmox_router::{list_subdirs_api_method, Permission, Router, RpcEnvironment, SubdirMap};
use proxmox_schema::api;
use proxmox_sys::sortable;
use pbs_api_types::{Authid, NODE_SCHEMA, SERVICE_ID_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY};
use pbs_api_types::{Authid, NODE_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY, SERVICE_ID_SCHEMA};
use proxmox_rest_server::WorkerTask;
@ -22,7 +22,6 @@ static SERVICE_NAME_LIST: [&str; 7] = [
];
pub fn real_service_name(service: &str) -> &str {
// since postfix package 3.1.0-3.1 the postfix unit is only here
// to manage subinstances, of which the default is called "-".
// This is where we look for the daemon status
@ -35,7 +34,6 @@ pub fn real_service_name(service: &str) -> &str {
}
fn get_full_service_state(service: &str) -> Result<Value, Error> {
let real_service_name = real_service_name(service);
let mut child = Command::new("systemctl")
@ -43,7 +41,7 @@ fn get_full_service_state(service: &str) -> Result<Value, Error> {
.stdout(Stdio::piped())
.spawn()?;
use std::io::{BufRead,BufReader};
use std::io::{BufRead, BufReader};
let mut result = json!({});
@ -76,7 +74,6 @@ fn get_full_service_state(service: &str) -> Result<Value, Error> {
}
fn json_service_state(service: &str, status: Value) -> Value {
if let Some(desc) = status["Description"].as_str() {
let name = status["Name"].as_str().unwrap_or(service);
let state = status["SubState"].as_str().unwrap_or("unknown");
@ -128,10 +125,7 @@ fn json_service_state(service: &str, status: Value) -> Value {
},
)]
/// Service list.
fn list_services(
_param: Value,
) -> Result<Value, Error> {
fn list_services(_param: Value) -> Result<Value, Error> {
let mut list = vec![];
for service in &SERVICE_NAME_LIST {
@ -165,11 +159,7 @@ fn list_services(
},
)]
/// Read service properties.
fn get_service_state(
service: String,
_param: Value,
) -> Result<Value, Error> {
fn get_service_state(service: String, _param: Value) -> Result<Value, Error> {
let service = service.as_str();
if !SERVICE_NAME_LIST.contains(&service) {
@ -182,11 +172,10 @@ fn get_service_state(
}
fn run_service_command(service: &str, cmd: &str, auth_id: Authid) -> Result<Value, Error> {
let workerid = format!("srv{}", &cmd);
let cmd = match cmd {
"start"|"stop"|"restart"=> cmd.to_string(),
"start" | "stop" | "restart" => cmd.to_string(),
"reload" => "try-reload-or-restart".to_string(), // some services do not implement reload
_ => bail!("unknown service command '{}'", cmd),
};
@ -198,9 +187,12 @@ fn run_service_command(service: &str, cmd: &str, auth_id: Authid) -> Result<Valu
auth_id.to_string(),
false,
move |_worker| {
if service == "proxmox-backup" && cmd == "stop" {
bail!("invalid service cmd '{} {}' cannot stop essential service!", service, cmd);
bail!(
"invalid service cmd '{} {}' cannot stop essential service!",
service,
cmd
);
}
let real_service_name = real_service_name(&service);
@ -214,7 +206,7 @@ fn run_service_command(service: &str, cmd: &str, auth_id: Authid) -> Result<Valu
}
Ok(())
}
},
)?;
Ok(upid.into())
@ -242,7 +234,6 @@ fn start_service(
_param: Value,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
log::info!("starting service {}", service);
@ -271,8 +262,7 @@ fn stop_service(
service: String,
_param: Value,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
) -> Result<Value, Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
log::info!("stopping service {}", service);
@ -302,7 +292,6 @@ fn restart_service(
_param: Value,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
log::info!("re-starting service {}", service);
@ -337,7 +326,6 @@ fn reload_service(
_param: Value,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
log::info!("reloading service {}", service);
@ -347,26 +335,11 @@ fn reload_service(
#[sortable]
const SERVICE_SUBDIRS: SubdirMap = &sorted!([
(
"reload", &Router::new()
.post(&API_METHOD_RELOAD_SERVICE)
),
(
"restart", &Router::new()
.post(&API_METHOD_RESTART_SERVICE)
),
(
"start", &Router::new()
.post(&API_METHOD_START_SERVICE)
),
(
"state", &Router::new()
.get(&API_METHOD_GET_SERVICE_STATE)
),
(
"stop", &Router::new()
.post(&API_METHOD_STOP_SERVICE)
),
("reload", &Router::new().post(&API_METHOD_RELOAD_SERVICE)),
("restart", &Router::new().post(&API_METHOD_RESTART_SERVICE)),
("start", &Router::new().post(&API_METHOD_START_SERVICE)),
("state", &Router::new().get(&API_METHOD_GET_SERVICE_STATE)),
("stop", &Router::new().post(&API_METHOD_STOP_SERVICE)),
]);
const SERVICE_ROUTER: Router = Router::new()

View File

@ -1,18 +1,18 @@
use std::process::Command;
use std::path::Path;
use std::process::Command;
use anyhow::{Error, format_err, bail};
use anyhow::{bail, format_err, Error};
use serde_json::Value;
use proxmox_sys::linux::procfs;
use proxmox_router::{ApiMethod, Router, RpcEnvironment, Permission};
use proxmox_router::{ApiMethod, Permission, Router, RpcEnvironment};
use proxmox_schema::api;
use pbs_api_types::{NODE_SCHEMA, NodePowerCommand, PRIV_SYS_AUDIT, PRIV_SYS_POWER_MANAGEMENT};
use pbs_api_types::{NodePowerCommand, NODE_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_POWER_MANAGEMENT};
use crate::api2::types::{
NodeCpuInformation, NodeStatus, NodeMemoryCounters, NodeSwapCounters, NodeInformation,
NodeCpuInformation, NodeInformation, NodeMemoryCounters, NodeStatus, NodeSwapCounters,
};
impl std::convert::From<procfs::ProcFsCPUInfo> for NodeCpuInformation {
@ -111,7 +111,6 @@ fn get_status(
)]
/// Reboot or shutdown the node.
fn reboot_or_shutdown(command: NodePowerCommand) -> Result<(), Error> {
let systemctl_command = match command {
NodePowerCommand::Reboot => "reboot",
NodePowerCommand::Shutdown => "poweroff",
@ -126,7 +125,13 @@ fn reboot_or_shutdown(command: NodePowerCommand) -> Result<(), Error> {
match output.status.code() {
Some(code) => {
let msg = String::from_utf8(output.stderr)
.map(|m| if m.is_empty() { String::from("no error message") } else { m })
.map(|m| {
if m.is_empty() {
String::from("no error message")
} else {
m
}
})
.unwrap_or_else(|_| String::from("non utf8 error message (suppressed)"));
bail!("diff failed with status code: {} - {}", code, msg);
}

View File

@ -1,16 +1,15 @@
use anyhow::{Error, format_err, bail};
use anyhow::{bail, format_err, Error};
use serde_json::Value;
use proxmox_router::{Router, RpcEnvironment, Permission};
use proxmox_router::{Permission, Router, RpcEnvironment};
use proxmox_schema::api;
use pbs_api_types::{
NODE_SCHEMA, SUBSCRIPTION_KEY_SCHEMA, Authid,
PRIV_SYS_AUDIT,PRIV_SYS_MODIFY,
Authid, NODE_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY, SUBSCRIPTION_KEY_SCHEMA,
};
use crate::tools;
use crate::tools::subscription::{self, SubscriptionStatus, SubscriptionInfo};
use crate::tools::subscription::{self, SubscriptionInfo, SubscriptionStatus};
use pbs_config::CachedUserInfo;
#[api(
@ -33,9 +32,7 @@ use pbs_config::CachedUserInfo;
},
)]
/// Check and update subscription status.
pub fn check_subscription(
force: bool,
) -> Result<(), Error> {
pub fn check_subscription(force: bool) -> Result<(), Error> {
let info = match subscription::read_subscription() {
Err(err) => bail!("could not read subscription status: {}", err),
Ok(Some(info)) => info,
@ -93,7 +90,7 @@ pub fn get_subscription(
status: SubscriptionStatus::NOTFOUND,
message: Some("There is no subscription key".into()),
serverid: Some(tools::get_hardware_address()?),
url: Some(url.into()),
url: Some(url.into()),
..Default::default()
},
};
@ -132,10 +129,7 @@ pub fn get_subscription(
},
)]
/// Set a subscription key and check it.
pub fn set_subscription(
key: String,
) -> Result<(), Error> {
pub fn set_subscription(key: String) -> Result<(), Error> {
let server_id = tools::get_hardware_address()?;
let info = subscription::check_subscription(key, server_id)?;
@ -161,7 +155,6 @@ pub fn set_subscription(
)]
/// Delete subscription info.
pub fn delete_subscription() -> Result<(), Error> {
subscription::delete_subscription()
.map_err(|err| format_err!("Deleting subscription failed: {}", err))?;

View File

@ -1,12 +1,12 @@
use std::process::{Command, Stdio};
use anyhow::{Error};
use anyhow::Error;
use serde_json::{json, Value};
use proxmox_router::{ApiMethod, Router, RpcEnvironment, Permission};
use proxmox_router::{ApiMethod, Permission, Router, RpcEnvironment};
use proxmox_schema::api;
use pbs_api_types::{NODE_SCHEMA, SYSTEMD_DATETIME_FORMAT, PRIV_SYS_AUDIT};
use pbs_api_types::{NODE_SCHEMA, PRIV_SYS_AUDIT, SYSTEMD_DATETIME_FORMAT};
fn dump_journal(
start: Option<u64>,
@ -15,12 +15,17 @@ fn dump_journal(
until: Option<&str>,
service: Option<&str>,
) -> Result<(u64, Vec<Value>), Error> {
let mut args = vec!["-o", "short", "--no-pager"];
if let Some(service) = service { args.extend(&["--unit", service]); }
if let Some(since) = since { args.extend(&["--since", since]); }
if let Some(until) = until { args.extend(&["--until", until]); }
if let Some(service) = service {
args.extend(&["--unit", service]);
}
if let Some(since) = since {
args.extend(&["--since", since]);
}
if let Some(until) = until {
args.extend(&["--until", until]);
}
let mut lines: Vec<Value> = vec![];
let mut limit = limit.unwrap_or(50);
@ -32,15 +37,19 @@ fn dump_journal(
.stdout(Stdio::piped())
.spawn()?;
use std::io::{BufRead,BufReader};
use std::io::{BufRead, BufReader};
if let Some(ref mut stdout) = child.stdout {
for line in BufReader::new(stdout).lines() {
match line {
Ok(line) => {
count += 1;
if count < start { continue };
if limit == 0 { continue };
if count < start {
continue;
};
if limit == 0 {
continue;
};
lines.push(json!({ "n": count, "t": line }));
@ -64,7 +73,7 @@ fn dump_journal(
// so we add a line
if count == 0 {
count += 1;
lines.push(json!({ "n": count, "t": "no content"}));
lines.push(json!({ "n": count, "t": "no content"}));
}
Ok((count, lines))
@ -133,21 +142,21 @@ fn get_syslog(
_info: &ApiMethod,
mut rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
let service = param["service"].as_str().map(|service| crate::api2::node::services::real_service_name(service));
let service = param["service"]
.as_str()
.map(|service| crate::api2::node::services::real_service_name(service));
let (count, lines) = dump_journal(
param["start"].as_u64(),
param["limit"].as_u64(),
param["since"].as_str(),
param["until"].as_str(),
service)?;
service,
)?;
rpcenv["total"] = Value::from(count);
Ok(json!(lines))
}
pub const ROUTER: Router = Router::new()
.get(&API_METHOD_GET_SYSLOG);
pub const ROUTER: Router = Router::new().get(&API_METHOD_GET_SYSLOG);

View File

@ -4,21 +4,20 @@ use std::io::{BufRead, BufReader};
use anyhow::{bail, Error};
use serde_json::{json, Value};
use proxmox_sys::sortable;
use proxmox_router::{list_subdirs_api_method, Router, RpcEnvironment, Permission, SubdirMap};
use proxmox_router::{list_subdirs_api_method, Permission, Router, RpcEnvironment, SubdirMap};
use proxmox_schema::api;
use proxmox_sys::sortable;
use pbs_api_types::{
Userid, Authid, Tokenname, TaskListItem, TaskStateType, UPID,
NODE_SCHEMA, UPID_SCHEMA, VERIFICATION_JOB_WORKER_ID_REGEX,
SYNC_JOB_WORKER_ID_REGEX, DATASTORE_SCHEMA,
Authid, TaskListItem, TaskStateType, Tokenname, Userid, DATASTORE_SCHEMA, NODE_SCHEMA,
PRIV_DATASTORE_MODIFY, PRIV_DATASTORE_VERIFY, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY,
SYNC_JOB_WORKER_ID_REGEX, UPID, UPID_SCHEMA, VERIFICATION_JOB_WORKER_ID_REGEX,
};
use crate::api2::pull::check_pull_privs;
use proxmox_rest_server::{upid_log_path, upid_read_status, TaskState, TaskListInfoIterator};
use pbs_config::CachedUserInfo;
use proxmox_rest_server::{upid_log_path, upid_read_status, TaskListInfoIterator, TaskState};
// matches respective job execution privileges
fn check_job_privs(auth_id: &Authid, user_info: &CachedUserInfo, upid: &UPID) -> Result<(), Error> {
@ -26,13 +25,15 @@ fn check_job_privs(auth_id: &Authid, user_info: &CachedUserInfo, upid: &UPID) ->
("verificationjob", Some(workerid)) => {
if let Some(captures) = VERIFICATION_JOB_WORKER_ID_REGEX.captures(workerid) {
if let Some(store) = captures.get(1) {
return user_info.check_privs(auth_id,
&["datastore", store.as_str()],
PRIV_DATASTORE_VERIFY,
true);
return user_info.check_privs(
auth_id,
&["datastore", store.as_str()],
PRIV_DATASTORE_VERIFY,
true,
);
}
}
},
}
("syncjob", Some(workerid)) => {
if let Some(captures) = SYNC_JOB_WORKER_ID_REGEX.captures(workerid) {
let remote = captures.get(1);
@ -40,29 +41,34 @@ fn check_job_privs(auth_id: &Authid, user_info: &CachedUserInfo, upid: &UPID) ->
let local_store = captures.get(3);
if let (Some(remote), Some(remote_store), Some(local_store)) =
(remote, remote_store, local_store) {
return check_pull_privs(auth_id,
local_store.as_str(),
remote.as_str(),
remote_store.as_str(),
false);
(remote, remote_store, local_store)
{
return check_pull_privs(
auth_id,
local_store.as_str(),
remote.as_str(),
remote_store.as_str(),
false,
);
}
}
},
}
("garbage_collection", Some(workerid)) => {
return user_info.check_privs(auth_id,
&["datastore", workerid],
PRIV_DATASTORE_MODIFY,
true)
},
return user_info.check_privs(
auth_id,
&["datastore", workerid],
PRIV_DATASTORE_MODIFY,
true,
)
}
("prune", Some(workerid)) => {
return user_info.check_privs(auth_id,
&["datastore",
workerid],
PRIV_DATASTORE_MODIFY,
true);
},
return user_info.check_privs(
auth_id,
&["datastore", workerid],
PRIV_DATASTORE_MODIFY,
true,
);
}
_ => bail!("not a scheduled job task"),
};
@ -102,7 +108,8 @@ fn check_job_store(upid: &UPID, store: &str) -> bool {
fn check_task_access(auth_id: &Authid, upid: &UPID) -> Result<(), Error> {
let task_auth_id: Authid = upid.auth_id.parse()?;
if auth_id == &task_auth_id
|| (task_auth_id.is_token() && &Authid::from(task_auth_id.user().clone()) == auth_id) {
|| (task_auth_id.is_token() && &Authid::from(task_auth_id.user().clone()) == auth_id)
{
// task owner can always read
Ok(())
} else {
@ -111,7 +118,8 @@ fn check_task_access(auth_id: &Authid, upid: &UPID) -> Result<(), Error> {
// access to all tasks
// or task == job which the user/token could have configured/manually executed
user_info.check_privs(auth_id, &["system", "tasks"], PRIV_SYS_AUDIT, false)
user_info
.check_privs(auth_id, &["system", "tasks"], PRIV_SYS_AUDIT, false)
.or_else(|_| check_job_privs(auth_id, &user_info, upid))
.or_else(|_| bail!("task access not allowed"))
}
@ -127,9 +135,10 @@ pub fn tasktype(state: &TaskState) -> TaskStateType {
}
fn into_task_list_item(info: proxmox_rest_server::TaskListInfo) -> pbs_api_types::TaskListItem {
let (endtime, status) = info
.state
.map_or_else(|| (None, None), |a| (Some(a.endtime()), Some(a.to_string())));
let (endtime, status) = info.state.map_or_else(
|| (None, None),
|a| (Some(a.endtime()), Some(a.to_string())),
);
pbs_api_types::TaskListItem {
upid: info.upid_str,
@ -210,11 +219,7 @@ fn into_task_list_item(info: proxmox_rest_server::TaskListInfo) -> pbs_api_types
},
)]
/// Get task status.
async fn get_task_status(
param: Value,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
async fn get_task_status(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<Value, Error> {
let upid = extract_upid(&param)?;
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
@ -249,7 +254,6 @@ async fn get_task_status(
}
fn extract_upid(param: &Value) -> Result<UPID, Error> {
let upid_str = pbs_tools::json::required_string_param(param, "upid")?;
upid_str.parse::<UPID>()
@ -289,11 +293,7 @@ fn extract_upid(param: &Value) -> Result<UPID, Error> {
},
)]
/// Read task log.
async fn read_task_log(
param: Value,
mut rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
async fn read_task_log(param: Value, mut rpcenv: &mut dyn RpcEnvironment) -> Result<Value, Error> {
let upid = extract_upid(&param)?;
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
@ -317,8 +317,12 @@ async fn read_task_log(
match line {
Ok(line) => {
count += 1;
if count < start { continue };
if limit == 0 { continue };
if count < start {
continue;
};
if limit == 0 {
continue;
};
lines.push(json!({ "n": count, "t": line }));
@ -359,11 +363,7 @@ async fn read_task_log(
},
)]
/// Try to stop a task.
fn stop_task(
param: Value,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
fn stop_task(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<Value, Error> {
let upid = extract_upid(&param)?;
let auth_id = rpcenv.get_auth_id().unwrap();
@ -465,7 +465,6 @@ pub fn list_tasks(
param: Value,
mut rpcenv: &mut dyn RpcEnvironment,
) -> Result<Vec<TaskListItem>, Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let user_info = CachedUserInfo::new()?;
let user_privs = user_info.lookup_privs(&auth_id, &["system", "tasks"]);
@ -475,7 +474,11 @@ pub fn list_tasks(
let store = param["store"].as_str();
let list = TaskListInfoIterator::new(running)?;
let limit = if limit > 0 { limit as usize } else { usize::MAX };
let limit = if limit > 0 {
limit as usize
} else {
usize::MAX
};
let mut skipped = 0;
let mut result: Vec<TaskListItem> = Vec::new();
@ -510,15 +513,21 @@ pub fn list_tasks(
}
if let Some(needle) = &userfilter {
if !info.upid.auth_id.to_string().contains(needle) { continue; }
if !info.upid.auth_id.to_string().contains(needle) {
continue;
}
}
if let Some(store) = store {
if !check_job_store(&info.upid, store) { continue; }
if !check_job_store(&info.upid, store) {
continue;
}
}
if let Some(typefilter) = &typefilter {
if !info.upid.worker_type.contains(typefilter) { continue; }
if !info.upid.worker_type.contains(typefilter) {
continue;
}
}
match (&info.state, &statusfilter) {
@ -528,9 +537,9 @@ pub fn list_tasks(
if !filters.contains(&tasktype(state)) {
continue;
}
},
}
(None, Some(_)) => continue,
_ => {},
_ => {}
}
if skipped < start as usize {
@ -546,7 +555,8 @@ pub fn list_tasks(
}
let mut count = result.len() + start as usize;
if !result.is_empty() && result.len() >= limit { // we have a 'virtual' entry as long as we have any new
if !result.is_empty() && result.len() >= limit {
// we have a 'virtual' entry as long as we have any new
count += 1;
}
@ -557,14 +567,8 @@ pub fn list_tasks(
#[sortable]
const UPID_API_SUBDIRS: SubdirMap = &sorted!([
(
"log", &Router::new()
.get(&API_METHOD_READ_TASK_LOG)
),
(
"status", &Router::new()
.get(&API_METHOD_GET_TASK_STATUS)
)
("log", &Router::new().get(&API_METHOD_READ_TASK_LOG)),
("status", &Router::new().get(&API_METHOD_GET_TASK_STATUS))
]);
pub const UPID_API_ROUTER: Router = Router::new()

View File

@ -1,11 +1,11 @@
use anyhow::{bail, format_err, Error};
use serde_json::{json, Value};
use proxmox_sys::fs::{file_read_firstline, replace_file, CreateOptions};
use proxmox_router::{Router, Permission};
use proxmox_router::{Permission, Router};
use proxmox_schema::api;
use proxmox_sys::fs::{file_read_firstline, replace_file, CreateOptions};
use pbs_api_types::{NODE_SCHEMA, TIME_ZONE_SCHEMA, PRIV_SYS_MODIFY};
use pbs_api_types::{NODE_SCHEMA, PRIV_SYS_MODIFY, TIME_ZONE_SCHEMA};
fn read_etc_localtime() -> Result<String, Error> {
// use /etc/timezone
@ -14,8 +14,8 @@ fn read_etc_localtime() -> Result<String, Error> {
}
// otherwise guess from the /etc/localtime symlink
let link = std::fs::read_link("/etc/localtime").
map_err(|err| format_err!("failed to guess timezone - {}", err))?;
let link = std::fs::read_link("/etc/localtime")
.map_err(|err| format_err!("failed to guess timezone - {}", err))?;
let link = link.to_string_lossy();
match link.rfind("/zoneinfo/") {
@ -87,17 +87,19 @@ fn get_time(_param: Value) -> Result<Value, Error> {
},
)]
/// Set time zone
fn set_timezone(
timezone: String,
_param: Value,
) -> Result<Value, Error> {
fn set_timezone(timezone: String, _param: Value) -> Result<Value, Error> {
let path = std::path::PathBuf::from(format!("/usr/share/zoneinfo/{}", timezone));
if !path.exists() {
bail!("No such timezone.");
}
replace_file("/etc/timezone", timezone.as_bytes(), CreateOptions::new(), true)?;
replace_file(
"/etc/timezone",
timezone.as_bytes(),
CreateOptions::new(),
true,
)?;
let _ = std::fs::remove_file("/etc/localtime");

View File

@ -1,9 +1,9 @@
//! Cheap check if the API daemon is online.
use anyhow::{Error};
use anyhow::Error;
use serde_json::{json, Value};
use proxmox_router::{Router, Permission};
use proxmox_router::{Permission, Router};
use proxmox_schema::api;
#[api(
@ -28,5 +28,4 @@ pub fn ping() -> Result<Value, Error> {
"pong": true,
}))
}
pub const ROUTER: Router = Router::new()
.get(&API_METHOD_PING);
pub const ROUTER: Router = Router::new().get(&API_METHOD_PING);

View File

@ -2,23 +2,22 @@
use std::convert::TryFrom;
use anyhow::{format_err, Error};
use futures::{select, future::FutureExt};
use futures::{future::FutureExt, select};
use proxmox_router::{ApiMethod, Permission, Router, RpcEnvironment};
use proxmox_schema::api;
use proxmox_router::{ApiMethod, Router, RpcEnvironment, Permission};
use proxmox_sys::task_log;
use pbs_api_types::{
Authid, SyncJobConfig, GroupFilter, RateLimitConfig, GROUP_FILTER_LIST_SCHEMA,
DATASTORE_SCHEMA, REMOTE_ID_SCHEMA, REMOVE_VANISHED_BACKUPS_SCHEMA,
PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_PRUNE, PRIV_REMOTE_READ,
Authid, GroupFilter, RateLimitConfig, SyncJobConfig, DATASTORE_SCHEMA,
GROUP_FILTER_LIST_SCHEMA, PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_PRUNE, PRIV_REMOTE_READ,
REMOTE_ID_SCHEMA, REMOVE_VANISHED_BACKUPS_SCHEMA,
};
use proxmox_rest_server::WorkerTask;
use pbs_config::CachedUserInfo;
use proxmox_rest_server::WorkerTask;
use crate::server::pull::{PullParameters, pull_store};
use crate::server::jobstate::Job;
use crate::server::pull::{pull_store, PullParameters};
pub fn check_pull_privs(
auth_id: &Authid,
@ -27,11 +26,15 @@ pub fn check_pull_privs(
remote_store: &str,
delete: bool,
) -> Result<(), Error> {
let user_info = CachedUserInfo::new()?;
user_info.check_privs(auth_id, &["datastore", store], PRIV_DATASTORE_BACKUP, false)?;
user_info.check_privs(auth_id, &["remote", remote, remote_store], PRIV_REMOTE_READ, false)?;
user_info.check_privs(
auth_id,
&["remote", remote, remote_store],
PRIV_REMOTE_READ,
false,
)?;
if delete {
user_info.check_privs(auth_id, &["datastore", store], PRIV_DATASTORE_PRUNE, false)?;
@ -48,7 +51,11 @@ impl TryFrom<&SyncJobConfig> for PullParameters {
&sync_job.store,
&sync_job.remote,
&sync_job.remote_store,
sync_job.owner.as_ref().unwrap_or_else(|| Authid::root_auth_id()).clone(),
sync_job
.owner
.as_ref()
.unwrap_or_else(|| Authid::root_auth_id())
.clone(),
sync_job.remove_vanished,
sync_job.group_filter.clone(),
sync_job.limit.clone(),
@ -63,12 +70,13 @@ pub fn do_sync_job(
schedule: Option<String>,
to_stdout: bool,
) -> Result<String, Error> {
let job_id = format!("{}:{}:{}:{}",
sync_job.remote,
sync_job.remote_store,
sync_job.store,
job.jobname());
let job_id = format!(
"{}:{}:{}:{}",
sync_job.remote,
sync_job.remote_store,
sync_job.store,
job.jobname()
);
let worker_type = job.jobtype().to_string();
let (email, notify) = crate::server::lookup_datastore_notify_settings(&sync_job.store);
@ -79,14 +87,12 @@ pub fn do_sync_job(
auth_id.to_string(),
to_stdout,
move |worker| async move {
job.start(&worker.upid().to_string())?;
let worker2 = worker.clone();
let sync_job2 = sync_job.clone();
let worker_future = async move {
let pull_params = PullParameters::try_from(&sync_job)?;
let client = pull_params.client().await?;
@ -109,9 +115,11 @@ pub fn do_sync_job(
Ok(())
};
let mut abort_future = worker2.abort_future().map(|_| Err(format_err!("sync aborted")));
let mut abort_future = worker2
.abort_future()
.map(|_| Err(format_err!("sync aborted")));
let result = select!{
let result = select! {
worker = worker_future.fuse() => worker,
abort = abort_future => abort,
};
@ -119,20 +127,23 @@ pub fn do_sync_job(
let status = worker2.create_state(&result);
match job.finish(status) {
Ok(_) => {},
Ok(_) => {}
Err(err) => {
eprintln!("could not finish job state: {}", err);
}
}
if let Some(email) = email {
if let Err(err) = crate::server::send_sync_status(&email, notify, &sync_job2, &result) {
if let Err(err) =
crate::server::send_sync_status(&email, notify, &sync_job2, &result)
{
eprintln!("send sync notification failed: {}", err);
}
}
result
})?;
},
)?;
Ok(upid_str)
}
@ -173,7 +184,7 @@ The delete flag additionally requires the Datastore.Prune privilege on '/datasto
},
)]
/// Sync store from other repository
async fn pull (
async fn pull(
store: String,
remote: String,
remote_store: String,
@ -183,7 +194,6 @@ async fn pull (
_info: &ApiMethod,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<String, Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let delete = remove_vanished.unwrap_or(false);
@ -201,25 +211,29 @@ async fn pull (
let client = pull_params.client().await?;
// fixme: set to_stdout to false?
let upid_str = WorkerTask::spawn("sync", Some(store.clone()), auth_id.to_string(), true, move |worker| async move {
let upid_str = WorkerTask::spawn(
"sync",
Some(store.clone()),
auth_id.to_string(),
true,
move |worker| async move {
task_log!(worker, "sync datastore '{}' start", store);
task_log!(worker, "sync datastore '{}' start", store);
let pull_future = pull_store(&worker, &client, &pull_params);
let future = select! {
success = pull_future.fuse() => success,
abort = worker.abort_future().map(|_| Err(format_err!("pull aborted"))) => abort,
};
let pull_future = pull_store(&worker, &client, &pull_params);
let future = select!{
success = pull_future.fuse() => success,
abort = worker.abort_future().map(|_| Err(format_err!("pull aborted"))) => abort,
};
let _ = future?;
let _ = future?;
task_log!(worker, "sync datastore '{}' end", store);
task_log!(worker, "sync datastore '{}' end", store);
Ok(())
})?;
Ok(())
},
)?;
Ok(upid_str)
}
pub const ROUTER: Router = Router::new()
.post(&API_METHOD_PULL);
pub const ROUTER: Router = Router::new().post(&API_METHOD_PULL);

View File

@ -1,13 +1,13 @@
use std::sync::{Arc,RwLock};
use std::collections::HashSet;
use std::sync::{Arc, RwLock};
use serde_json::{json, Value};
use proxmox_router::{RpcEnvironment, RpcEnvironmentType};
use pbs_api_types::Authid;
use pbs_datastore::backup_info::BackupDir;
use pbs_datastore::DataStore;
use pbs_api_types::Authid;
use proxmox_rest_server::formatter::*;
use proxmox_rest_server::WorkerTask;
@ -22,7 +22,7 @@ pub struct ReaderEnvironment {
pub worker: Arc<WorkerTask>,
pub datastore: Arc<DataStore>,
pub backup_dir: BackupDir,
allowed_chunks: Arc<RwLock<HashSet<[u8;32]>>>,
allowed_chunks: Arc<RwLock<HashSet<[u8; 32]>>>,
}
impl ReaderEnvironment {
@ -33,8 +33,6 @@ impl ReaderEnvironment {
datastore: Arc<DataStore>,
backup_dir: BackupDir,
) -> Self {
Self {
result_attributes: json!({}),
env_type,
@ -53,22 +51,22 @@ impl ReaderEnvironment {
}
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);
}
}
pub fn register_chunk(&self, digest: [u8;32]) {
pub fn register_chunk(&self, digest: [u8; 32]) {
let mut allowed_chunks = self.allowed_chunks.write().unwrap();
allowed_chunks.insert(digest);
}
pub fn check_chunk_access(&self, digest: [u8;32]) -> bool {
self.allowed_chunks.read().unwrap().contains(&digest)
pub fn check_chunk_access(&self, digest: [u8; 32]) -> bool {
self.allowed_chunks.read().unwrap().contains(&digest)
}
}
impl RpcEnvironment for ReaderEnvironment {
fn result_attrib_mut(&mut self) -> &mut Value {
&mut self.result_attributes
}

View File

@ -2,58 +2,66 @@
use anyhow::{bail, format_err, Error};
use futures::*;
use hex::FromHex;
use hyper::header::{self, HeaderValue, UPGRADE};
use hyper::http::request::Parts;
use hyper::{Body, Response, Request, StatusCode};
use hyper::{Body, Request, Response, StatusCode};
use serde_json::Value;
use hex::FromHex;
use proxmox_sys::sortable;
use proxmox_router::{
http_err, list_subdirs_api_method, ApiHandler, ApiMethod, ApiResponseFuture, Permission,
Router, RpcEnvironment, SubdirMap,
};
use proxmox_schema::{BooleanSchema, ObjectSchema};
use proxmox_sys::sortable;
use pbs_api_types::{
Authid, Operation, DATASTORE_SCHEMA, BACKUP_TYPE_SCHEMA, BACKUP_TIME_SCHEMA,
BACKUP_ID_SCHEMA, CHUNK_DIGEST_SCHEMA, PRIV_DATASTORE_READ, PRIV_DATASTORE_BACKUP,
BACKUP_ARCHIVE_NAME_SCHEMA,
Authid, Operation, BACKUP_ARCHIVE_NAME_SCHEMA, BACKUP_ID_SCHEMA, BACKUP_TIME_SCHEMA,
BACKUP_TYPE_SCHEMA, CHUNK_DIGEST_SCHEMA, DATASTORE_SCHEMA, PRIV_DATASTORE_BACKUP,
PRIV_DATASTORE_READ,
};
use proxmox_sys::fs::lock_dir_noblock_shared;
use pbs_tools::json::{required_integer_param, required_string_param};
use pbs_datastore::{DataStore, PROXMOX_BACKUP_READER_PROTOCOL_ID_V1};
use pbs_config::CachedUserInfo;
use pbs_datastore::backup_info::BackupDir;
use pbs_datastore::index::IndexFile;
use pbs_datastore::manifest::{archive_type, ArchiveType};
use pbs_config::CachedUserInfo;
use proxmox_rest_server::{WorkerTask, H2Service};
use pbs_datastore::{DataStore, PROXMOX_BACKUP_READER_PROTOCOL_ID_V1};
use pbs_tools::json::{required_integer_param, required_string_param};
use proxmox_rest_server::{H2Service, WorkerTask};
use proxmox_sys::fs::lock_dir_noblock_shared;
use crate::api2::helpers;
mod environment;
use environment::*;
pub const ROUTER: Router = Router::new()
.upgrade(&API_METHOD_UPGRADE_BACKUP);
pub const ROUTER: Router = Router::new().upgrade(&API_METHOD_UPGRADE_BACKUP);
#[sortable]
pub const API_METHOD_UPGRADE_BACKUP: ApiMethod = ApiMethod::new(
&ApiHandler::AsyncHttp(&upgrade_to_backup_reader_protocol),
&ObjectSchema::new(
concat!("Upgraded to backup protocol ('", PROXMOX_BACKUP_READER_PROTOCOL_ID_V1!(), "')."),
concat!(
"Upgraded to backup protocol ('",
PROXMOX_BACKUP_READER_PROTOCOL_ID_V1!(),
"')."
),
&sorted!([
("store", false, &DATASTORE_SCHEMA),
("backup-type", false, &BACKUP_TYPE_SCHEMA),
("backup-id", false, &BACKUP_ID_SCHEMA),
("backup-time", false, &BACKUP_TIME_SCHEMA),
("debug", true, &BooleanSchema::new("Enable verbose debug logging.").schema()),
(
"debug",
true,
&BooleanSchema::new("Enable verbose debug logging.").schema()
),
]),
)
).access(
),
)
.access(
// Note: parameter 'store' is no uri parameter, so we need to test inside function body
Some("The user needs Datastore.Read privilege on /datastore/{store}."),
&Permission::Anybody
&Permission::Anybody,
);
fn upgrade_to_backup_reader_protocol(
@ -63,7 +71,6 @@ fn upgrade_to_backup_reader_protocol(
_info: &ApiMethod,
rpcenv: Box<dyn RpcEnvironment>,
) -> ApiResponseFuture {
async move {
let debug = param["debug"].as_bool().unwrap_or(false);
@ -91,14 +98,17 @@ fn upgrade_to_backup_reader_protocol(
.headers
.get("UPGRADE")
.ok_or_else(|| format_err!("missing Upgrade header"))?
.to_str()?;
.to_str()?;
if protocols != PROXMOX_BACKUP_READER_PROTOCOL_ID_V1!() {
bail!("invalid protocol name");
}
if parts.version >= http::version::Version::HTTP_2 {
bail!("unexpected http version '{:?}' (expected version < 2)", parts.version);
if parts.version >= http::version::Version::HTTP_2 {
bail!(
"unexpected http version '{:?}' (expected version < 2)",
parts.version
);
}
let env_type = rpcenv.env_type();
@ -107,8 +117,7 @@ fn upgrade_to_backup_reader_protocol(
if !priv_read {
let owner = datastore.get_owner(backup_dir.group())?;
let correct_owner = owner == auth_id
|| (owner.is_token()
&& Authid::from(owner.user().clone()) == auth_id);
|| (owner.is_token() && Authid::from(owner.user().clone()) == auth_id);
if !correct_owner {
bail!("backup owner check failed!");
}
@ -117,83 +126,100 @@ fn upgrade_to_backup_reader_protocol(
let _guard = lock_dir_noblock_shared(
&datastore.snapshot_path(&backup_dir),
"snapshot",
"locked by another operation")?;
"locked by another operation",
)?;
let path = datastore.base_path();
//let files = BackupInfo::list_files(&path, &backup_dir)?;
let worker_id = format!("{}:{}/{}/{:08X}", store, backup_type, backup_id, backup_dir.backup_time());
let worker_id = format!(
"{}:{}/{}/{:08X}",
store,
backup_type,
backup_id,
backup_dir.backup_time()
);
WorkerTask::spawn("reader", Some(worker_id), auth_id.to_string(), true, move |worker| async move {
let _guard = _guard;
WorkerTask::spawn(
"reader",
Some(worker_id),
auth_id.to_string(),
true,
move |worker| async move {
let _guard = _guard;
let mut env = ReaderEnvironment::new(
env_type,
auth_id,
worker.clone(),
datastore,
backup_dir,
);
let mut env = ReaderEnvironment::new(
env_type,
auth_id,
worker.clone(),
datastore,
backup_dir,
);
env.debug = debug;
env.debug = debug;
env.log(format!("starting new backup reader datastore '{}': {:?}", store, path));
env.log(format!(
"starting new backup reader datastore '{}': {:?}",
store, path
));
let service = H2Service::new(env.clone(), worker.clone(), &READER_API_ROUTER, debug);
let service =
H2Service::new(env.clone(), worker.clone(), &READER_API_ROUTER, debug);
let mut abort_future = worker.abort_future()
.map(|_| Err(format_err!("task aborted")));
let mut abort_future = worker
.abort_future()
.map(|_| Err(format_err!("task aborted")));
let env2 = env.clone();
let req_fut = async move {
let conn = hyper::upgrade::on(Request::from_parts(parts, req_body)).await?;
env2.debug("protocol upgrade done");
let env2 = env.clone();
let req_fut = async move {
let conn = hyper::upgrade::on(Request::from_parts(parts, req_body)).await?;
env2.debug("protocol upgrade done");
let mut http = hyper::server::conn::Http::new();
http.http2_only(true);
// increase window size: todo - find optiomal size
let window_size = 32*1024*1024; // max = (1 << 31) - 2
http.http2_initial_stream_window_size(window_size);
http.http2_initial_connection_window_size(window_size);
http.http2_max_frame_size(4*1024*1024);
let mut http = hyper::server::conn::Http::new();
http.http2_only(true);
// increase window size: todo - find optiomal size
let window_size = 32 * 1024 * 1024; // max = (1 << 31) - 2
http.http2_initial_stream_window_size(window_size);
http.http2_initial_connection_window_size(window_size);
http.http2_max_frame_size(4 * 1024 * 1024);
http.serve_connection(conn, service)
.map_err(Error::from).await
};
http.serve_connection(conn, service)
.map_err(Error::from)
.await
};
futures::select!{
req = req_fut.fuse() => req?,
abort = abort_future => abort?,
};
futures::select! {
req = req_fut.fuse() => req?,
abort = abort_future => abort?,
};
env.log("reader finished successfully");
env.log("reader finished successfully");
Ok(())
})?;
Ok(())
},
)?;
let response = Response::builder()
.status(StatusCode::SWITCHING_PROTOCOLS)
.header(UPGRADE, HeaderValue::from_static(PROXMOX_BACKUP_READER_PROTOCOL_ID_V1!()))
.header(
UPGRADE,
HeaderValue::from_static(PROXMOX_BACKUP_READER_PROTOCOL_ID_V1!()),
)
.body(Body::empty())?;
Ok(response)
}.boxed()
}
.boxed()
}
const READER_API_SUBDIRS: SubdirMap = &[
("chunk", &Router::new().download(&API_METHOD_DOWNLOAD_CHUNK)),
(
"chunk", &Router::new()
.download(&API_METHOD_DOWNLOAD_CHUNK)
),
(
"download", &Router::new()
.download(&API_METHOD_DOWNLOAD_FILE)
),
(
"speedtest", &Router::new()
.download(&API_METHOD_SPEEDTEST)
"download",
&Router::new().download(&API_METHOD_DOWNLOAD_FILE),
),
("speedtest", &Router::new().download(&API_METHOD_SPEEDTEST)),
];
pub const READER_API_ROUTER: Router = Router::new()
@ -205,10 +231,8 @@ pub const API_METHOD_DOWNLOAD_FILE: ApiMethod = ApiMethod::new(
&ApiHandler::AsyncHttp(&download_file),
&ObjectSchema::new(
"Download specified file.",
&sorted!([
("file-name", false, &BACKUP_ARCHIVE_NAME_SCHEMA),
]),
)
&sorted!([("file-name", false, &BACKUP_ARCHIVE_NAME_SCHEMA),]),
),
);
fn download_file(
@ -218,7 +242,6 @@ fn download_file(
_info: &ApiMethod,
rpcenv: Box<dyn RpcEnvironment>,
) -> ApiResponseFuture {
async move {
let env: &ReaderEnvironment = rpcenv.as_ref();
@ -239,11 +262,14 @@ fn download_file(
let index = env.datastore.open_dynamic_reader(&path)?;
Some(Box::new(index))
}
_ => { None }
_ => None,
};
if let Some(index) = index {
env.log(format!("register chunks in '{}' as downloadable.", file_name));
env.log(format!(
"register chunks in '{}' as downloadable.",
file_name
));
for pos in 0..index.index_count() {
let info = index.chunk_info(pos).unwrap();
@ -252,7 +278,8 @@ fn download_file(
}
helpers::create_download_response(path).await
}.boxed()
}
.boxed()
}
#[sortable]
@ -260,10 +287,8 @@ pub const API_METHOD_DOWNLOAD_CHUNK: ApiMethod = ApiMethod::new(
&ApiHandler::AsyncHttp(&download_chunk),
&ObjectSchema::new(
"Download specified chunk.",
&sorted!([
("digest", false, &CHUNK_DIGEST_SCHEMA),
]),
)
&sorted!([("digest", false, &CHUNK_DIGEST_SCHEMA),]),
),
);
fn download_chunk(
@ -273,7 +298,6 @@ fn download_chunk(
_info: &ApiMethod,
rpcenv: Box<dyn RpcEnvironment>,
) -> ApiResponseFuture {
async move {
let env: &ReaderEnvironment = rpcenv.as_ref();
@ -281,8 +305,15 @@ fn download_chunk(
let digest = <[u8; 32]>::from_hex(digest_str)?;
if !env.check_chunk_access(digest) {
env.log(format!("attempted to download chunk {} which is not in registered chunk list", digest_str));
return Err(http_err!(UNAUTHORIZED, "download chunk {} not allowed", digest_str));
env.log(format!(
"attempted to download chunk {} which is not in registered chunk list",
digest_str
));
return Err(http_err!(
UNAUTHORIZED,
"download chunk {} not allowed",
digest_str
));
}
let (path, _) = env.datastore.chunk_path(&digest);
@ -290,18 +321,21 @@ fn download_chunk(
env.debug(format!("download chunk {:?}", path));
let data = proxmox_async::runtime::block_in_place(|| std::fs::read(path))
.map_err(move |err| http_err!(BAD_REQUEST, "reading file {:?} failed: {}", path2, err))?;
let data =
proxmox_async::runtime::block_in_place(|| std::fs::read(path)).map_err(move |err| {
http_err!(BAD_REQUEST, "reading file {:?} failed: {}", path2, err)
})?;
let body = Body::from(data);
// fixme: set other headers ?
Ok(Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, "application/octet-stream")
.body(body)
.unwrap())
}.boxed()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, "application/octet-stream")
.body(body)
.unwrap())
}
.boxed()
}
/* this is too slow
@ -347,7 +381,7 @@ fn download_chunk_old(
pub const API_METHOD_SPEEDTEST: ApiMethod = ApiMethod::new(
&ApiHandler::AsyncHttp(&speedtest),
&ObjectSchema::new("Test 1M block download speed.", &[])
&ObjectSchema::new("Test 1M block download speed.", &[]),
);
fn speedtest(
@ -357,8 +391,7 @@ fn speedtest(
_info: &ApiMethod,
_rpcenv: Box<dyn RpcEnvironment>,
) -> ApiResponseFuture {
let buffer = vec![65u8; 1024*1024]; // nonsense [A,A,A...]
let buffer = vec![65u8; 1024 * 1024]; // nonsense [A,A,A...]
let body = Body::from(buffer);

View File

@ -3,26 +3,20 @@
use anyhow::Error;
use serde_json::Value;
use proxmox_schema::api;
use proxmox_router::{
ApiMethod,
Permission,
Router,
RpcEnvironment,
SubdirMap,
};
use proxmox_router::list_subdirs_api_method;
use proxmox_router::{ApiMethod, Permission, Router, RpcEnvironment, SubdirMap};
use proxmox_schema::api;
use pbs_api_types::{
Authid, DataStoreStatusListItem, Operation, RRDMode, RRDTimeFrame,
PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_BACKUP,
Authid, DataStoreStatusListItem, Operation, RRDMode, RRDTimeFrame, PRIV_DATASTORE_AUDIT,
PRIV_DATASTORE_BACKUP,
};
use pbs_datastore::DataStore;
use pbs_config::CachedUserInfo;
use pbs_datastore::DataStore;
use crate::tools::statistics::{linear_regression};
use crate::rrd_cache::extract_rrd_data;
use crate::tools::statistics::linear_regression;
#[api(
returns: {
@ -41,8 +35,7 @@ pub fn datastore_status(
_param: Value,
_info: &ApiMethod,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Vec<DataStoreStatusListItem>, Error> {
) -> Result<Vec<DataStoreStatusListItem>, Error> {
let (config, _digest) = pbs_config::datastore::config()?;
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
@ -52,7 +45,7 @@ pub fn datastore_status(
for (store, (_, _)) in &config.sections {
let user_privs = user_info.lookup_privs(&auth_id, &["datastore", store]);
let allowed = (user_privs & (PRIV_DATASTORE_AUDIT| PRIV_DATASTORE_BACKUP)) != 0;
let allowed = (user_privs & (PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_BACKUP)) != 0;
if !allowed {
continue;
}
@ -90,12 +83,8 @@ pub fn datastore_status(
let rrd_dir = format!("datastore/{}", store);
let get_rrd = |what: &str| extract_rrd_data(
&rrd_dir,
what,
RRDTimeFrame::Month,
RRDMode::Average,
);
let get_rrd =
|what: &str| extract_rrd_data(&rrd_dir, what, RRDTimeFrame::Month, RRDMode::Average);
let total_res = get_rrd("total")?;
let used_res = get_rrd("used")?;
@ -114,14 +103,12 @@ pub fn datastore_status(
match (total, used) {
(Some(total), Some(used)) if total != 0.0 => {
time_list.push(start + (idx as u64)*reso);
let usage = used/total;
time_list.push(start + (idx as u64) * reso);
let usage = used / total;
usage_list.push(usage);
history.push(Some(usage));
},
_ => {
history.push(None)
}
_ => history.push(None),
}
}
@ -145,9 +132,10 @@ pub fn datastore_status(
Ok(list.into())
}
const SUBDIRS: SubdirMap = &[
("datastore-usage", &Router::new().get(&API_METHOD_DATASTORE_STATUS)),
];
const SUBDIRS: SubdirMap = &[(
"datastore-usage",
&Router::new().get(&API_METHOD_DATASTORE_STATUS),
)];
pub const ROUTER: Router = Router::new()
.get(&list_subdirs_api_method!(SUBDIRS))

View File

@ -1,11 +1,9 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
use proxmox_schema::{api, ApiType, Schema, StringSchema, ApiStringFormat};
use proxmox_schema::{api, ApiStringFormat, ApiType, Schema, StringSchema};
use pbs_api_types::{
DNS_ALIAS_FORMAT, DNS_NAME_FORMAT, PROXMOX_SAFE_ID_FORMAT,
};
use pbs_api_types::{DNS_ALIAS_FORMAT, DNS_NAME_FORMAT, PROXMOX_SAFE_ID_FORMAT};
#[api(
properties: {
@ -41,10 +39,10 @@ pub struct AcmeDomain {
pub plugin: Option<String>,
}
pub const ACME_DOMAIN_PROPERTY_SCHEMA: Schema = StringSchema::new(
"ACME domain configuration string")
.format(&ApiStringFormat::PropertyString(&AcmeDomain::API_SCHEMA))
.schema();
pub const ACME_DOMAIN_PROPERTY_SCHEMA: Schema =
StringSchema::new("ACME domain configuration string")
.format(&ApiStringFormat::PropertyString(&AcmeDomain::API_SCHEMA))
.schema();
#[api(
properties: {

View File

@ -21,12 +21,10 @@ pub const FILENAME_FORMAT: ApiStringFormat = ApiStringFormat::VerifyFn(|name| {
Ok(())
});
// Regression tests
#[test]
fn test_cert_fingerprint_schema() -> Result<(), anyhow::Error> {
let schema = pbs_api_types::CERT_FINGERPRINT_SHA256_SCHEMA;
let invalid_fingerprints = [
@ -40,7 +38,10 @@ fn test_cert_fingerprint_schema() -> Result<(), anyhow::Error> {
for fingerprint in invalid_fingerprints.iter() {
if schema.parse_simple_value(fingerprint).is_ok() {
bail!("test fingerprint '{}' failed - got Ok() while exception an error.", fingerprint);
bail!(
"test fingerprint '{}' failed - got Ok() while exception an error.",
fingerprint
);
}
}
@ -58,7 +59,11 @@ fn test_cert_fingerprint_schema() -> Result<(), anyhow::Error> {
};
if v != serde_json::json!(fingerprint) {
bail!("unable to parse fingerprint '{}' - got wrong value {:?}", fingerprint, v);
bail!(
"unable to parse fingerprint '{}' - got wrong value {:?}",
fingerprint,
v
);
}
}
@ -67,24 +72,26 @@ fn test_cert_fingerprint_schema() -> Result<(), anyhow::Error> {
#[test]
fn test_proxmox_user_id_schema() -> Result<(), anyhow::Error> {
use pbs_api_types::Userid;
let invalid_user_ids = [
"x", // too short
"xx", // too short
"xxx", // no realm
"xxx@", // no realm
"xx x@test", // contains space
"x", // too short
"xx", // too short
"xxx", // no realm
"xxx@", // no realm
"xx x@test", // contains space
"xx\nx@test", // contains control character
"x:xx@test", // contains collon
"xx/x@test", // contains slash
"x:xx@test", // contains collon
"xx/x@test", // contains slash
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@test", // too long
];
for name in invalid_user_ids.iter() {
if Userid::API_SCHEMA.parse_simple_value(name).is_ok() {
bail!("test userid '{}' failed - got Ok() while exception an error.", name);
bail!(
"test userid '{}' failed - got Ok() while exception an error.",
name
);
}
}
@ -105,7 +112,11 @@ fn test_proxmox_user_id_schema() -> Result<(), anyhow::Error> {
};
if v != serde_json::json!(name) {
bail!("unable to parse userid '{}' - got wrong value {:?}", name, v);
bail!(
"unable to parse userid '{}' - got wrong value {:?}",
name,
v
);
}
}
@ -139,7 +150,7 @@ pub struct NodeSwapCounters {
}
#[api]
#[derive(Serialize,Deserialize,Default)]
#[derive(Serialize, Deserialize, Default)]
#[serde(rename_all = "kebab-case")]
/// Contains general node information such as the fingerprint`
pub struct NodeInformation {
@ -207,13 +218,13 @@ pub struct NodeStatus {
pub info: NodeInformation,
}
pub const HTTP_PROXY_SCHEMA: Schema = StringSchema::new(
"HTTP proxy configuration [http://]<host>[:port]")
.format(&ApiStringFormat::VerifyFn(|s| {
proxmox_http::ProxyConfig::parse_proxy_url(s)?;
Ok(())
}))
.min_length(1)
.max_length(128)
.type_text("[http://]<host>[:port]")
.schema();
pub const HTTP_PROXY_SCHEMA: Schema =
StringSchema::new("HTTP proxy configuration [http://]<host>[:port]")
.format(&ApiStringFormat::VerifyFn(|s| {
proxmox_http::ProxyConfig::parse_proxy_url(s)?;
Ok(())
}))
.min_length(1)
.max_length(128)
.type_text("[http://]<host>[:port]")
.schema();

View File

@ -1,9 +1,9 @@
//! Version information
use anyhow::{Error};
use anyhow::Error;
use serde_json::{json, Value};
use proxmox_router::{ApiHandler, ApiMethod, Router, RpcEnvironment, Permission};
use proxmox_router::{ApiHandler, ApiMethod, Permission, Router, RpcEnvironment};
use proxmox_schema::ObjectSchema;
fn get_version(
@ -11,7 +11,6 @@ fn get_version(
_info: &ApiMethod,
_rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
Ok(json!({
"version": pbs_buildcfg::PROXMOX_PKG_VERSION,
"release": pbs_buildcfg::PROXMOX_PKG_RELEASE,
@ -19,11 +18,10 @@ fn get_version(
}))
}
pub const ROUTER: Router = Router::new()
.get(
&ApiMethod::new(
&ApiHandler::Sync(&get_version),
&ObjectSchema::new("Proxmox Backup Server API version.", &[])
).access(None, &Permission::Anybody)
);
pub const ROUTER: Router = Router::new().get(
&ApiMethod::new(
&ApiHandler::Sync(&get_version),
&ObjectSchema::new("Proxmox Backup Server API version.", &[]),
)
.access(None, &Permission::Anybody),
);