1 use std
::path
::PathBuf
;
3 use anyhow
::{bail, Error}
;
5 use ::serde
::{Deserialize, Serialize}
;
7 use proxmox_router
::{Router, RpcEnvironment, RpcEnvironmentType, Permission}
;
8 use proxmox_schema
::{api, ApiType, parse_property_string}
;
9 use proxmox_section_config
::SectionConfigData
;
11 use pbs_datastore
::chunk_store
::ChunkStore
;
12 use pbs_config
::BackupLockGuard
;
14 Authid
, DatastoreNotify
,
15 DATASTORE_SCHEMA
, PROXMOX_CONFIG_DIGEST_SCHEMA
,
16 PRIV_DATASTORE_ALLOCATE
, PRIV_DATASTORE_AUDIT
, PRIV_DATASTORE_MODIFY
,
17 DataStoreConfig
, DataStoreConfigUpdater
,
20 use crate::api2
::config
::sync
::delete_sync_job
;
21 use crate::api2
::config
::verify
::delete_verification_job
;
22 use crate::api2
::config
::tape_backup_job
::{list_tape_backup_jobs, delete_tape_backup_job}
;
23 use crate::api2
::admin
::{
25 verify
::list_verification_jobs
,
27 use pbs_config
::CachedUserInfo
;
28 use pbs_tools
::task
::WorkerTaskContext
;
30 use proxmox_rest_server
::WorkerTask
;
32 use crate::server
::jobstate
;
39 description
: "List the configured datastores (with config digest).",
41 items
: { type: DataStoreConfig }
,
44 permission
: &Permission
::Anybody
,
47 /// List all datastores
48 pub fn list_datastores(
50 mut rpcenv
: &mut dyn RpcEnvironment
,
51 ) -> Result
<Vec
<DataStoreConfig
>, Error
> {
53 let (config
, digest
) = pbs_config
::datastore
::config()?
;
55 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
56 let user_info
= CachedUserInfo
::new()?
;
58 rpcenv
["digest"] = proxmox
::tools
::digest_to_hex(&digest
).into();
60 let list
:Vec
<DataStoreConfig
> = config
.convert_to_typed_array("datastore")?
;
61 let filter_by_privs
= |store
: &DataStoreConfig
| {
62 let user_privs
= user_info
.lookup_privs(&auth_id
, &["datastore", &store
.name
]);
63 (user_privs
& PRIV_DATASTORE_AUDIT
) != 0
66 Ok(list
.into_iter().filter(filter_by_privs
).collect())
69 pub(crate) fn do_create_datastore(
70 _lock
: BackupLockGuard
,
71 mut config
: SectionConfigData
,
72 datastore
: DataStoreConfig
,
73 worker
: Option
<&dyn WorkerTaskContext
>,
74 ) -> Result
<(), Error
> {
75 let path
: PathBuf
= datastore
.path
.clone().into();
77 let backup_user
= pbs_config
::backup_user()?
;
78 let _store
= ChunkStore
::create(&datastore
.name
, path
, backup_user
.uid
, backup_user
.gid
, worker
)?
;
80 config
.set_data(&datastore
.name
, "datastore", &datastore
)?
;
82 pbs_config
::datastore
::save_config(&config
)?
;
84 jobstate
::create_state_file("prune", &datastore
.name
)?
;
85 jobstate
::create_state_file("garbage_collection", &datastore
.name
)?
;
95 type: DataStoreConfig
,
101 permission
: &Permission
::Privilege(&["datastore"], PRIV_DATASTORE_ALLOCATE
, false),
104 /// Create new datastore config.
105 pub fn create_datastore(
106 config
: DataStoreConfig
,
107 rpcenv
: &mut dyn RpcEnvironment
,
108 ) -> Result
<String
, Error
> {
110 let lock
= pbs_config
::datastore
::lock_config()?
;
112 let (section_config
, _digest
) = pbs_config
::datastore
::config()?
;
114 if section_config
.sections
.get(&config
.name
).is_some() {
115 bail
!("datastore '{}' already exists.", config
.name
);
118 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
119 let to_stdout
= rpcenv
.env_type() == RpcEnvironmentType
::CLI
;
121 WorkerTask
::new_thread(
123 Some(config
.name
.to_string()),
126 move |worker
| do_create_datastore(lock
, section_config
, config
, Some(&worker
)),
134 schema
: DATASTORE_SCHEMA
,
138 returns
: { type: DataStoreConfig }
,
140 permission
: &Permission
::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_AUDIT
, false),
143 /// Read a datastore configuration.
144 pub fn read_datastore(
146 mut rpcenv
: &mut dyn RpcEnvironment
,
147 ) -> Result
<DataStoreConfig
, Error
> {
148 let (config
, digest
) = pbs_config
::datastore
::config()?
;
150 let store_config
= config
.lookup("datastore", &name
)?
;
151 rpcenv
["digest"] = proxmox
::tools
::digest_to_hex(&digest
).into();
157 #[derive(Serialize, Deserialize)]
158 #[serde(rename_all="kebab-case")]
159 #[allow(non_camel_case_types)]
160 /// Deletable property name
161 pub enum DeletableProperty
{
162 /// Delete the comment property.
164 /// Delete the garbage collection schedule.
166 /// Delete the prune job schedule.
168 /// Delete the keep-last property
170 /// Delete the keep-hourly property
172 /// Delete the keep-daily property
174 /// Delete the keep-weekly property
176 /// Delete the keep-monthly property
178 /// Delete the keep-yearly property
180 /// Delete the verify-new property
182 /// Delete the notify-user property
184 /// Delete the notify property
193 schema
: DATASTORE_SCHEMA
,
196 type: DataStoreConfigUpdater
,
200 description
: "List of properties to delete.",
204 type: DeletableProperty
,
209 schema
: PROXMOX_CONFIG_DIGEST_SCHEMA
,
214 permission
: &Permission
::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_MODIFY
, false),
217 /// Update datastore config.
218 pub fn update_datastore(
219 update
: DataStoreConfigUpdater
,
221 delete
: Option
<Vec
<DeletableProperty
>>,
222 digest
: Option
<String
>,
223 ) -> Result
<(), Error
> {
225 let _lock
= pbs_config
::datastore
::lock_config()?
;
227 // pass/compare digest
228 let (mut config
, expected_digest
) = pbs_config
::datastore
::config()?
;
230 if let Some(ref digest
) = digest
{
231 let digest
= proxmox
::tools
::hex_to_digest(digest
)?
;
232 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
235 let mut data
: DataStoreConfig
= config
.lookup("datastore", &name
)?
;
237 if let Some(delete
) = delete
{
238 for delete_prop
in delete
{
240 DeletableProperty
::comment
=> { data.comment = None; }
,
241 DeletableProperty
::gc_schedule
=> { data.gc_schedule = None; }
,
242 DeletableProperty
::prune_schedule
=> { data.prune_schedule = None; }
,
243 DeletableProperty
::keep_last
=> { data.keep_last = None; }
,
244 DeletableProperty
::keep_hourly
=> { data.keep_hourly = None; }
,
245 DeletableProperty
::keep_daily
=> { data.keep_daily = None; }
,
246 DeletableProperty
::keep_weekly
=> { data.keep_weekly = None; }
,
247 DeletableProperty
::keep_monthly
=> { data.keep_monthly = None; }
,
248 DeletableProperty
::keep_yearly
=> { data.keep_yearly = None; }
,
249 DeletableProperty
::verify_new
=> { data.verify_new = None; }
,
250 DeletableProperty
::notify
=> { data.notify = None; }
,
251 DeletableProperty
::notify_user
=> { data.notify_user = None; }
,
256 if let Some(comment
) = update
.comment
{
257 let comment
= comment
.trim().to_string();
258 if comment
.is_empty() {
261 data
.comment
= Some(comment
);
265 let mut gc_schedule_changed
= false;
266 if update
.gc_schedule
.is_some() {
267 gc_schedule_changed
= data
.gc_schedule
!= update
.gc_schedule
;
268 data
.gc_schedule
= update
.gc_schedule
;
271 let mut prune_schedule_changed
= false;
272 if update
.prune_schedule
.is_some() {
273 prune_schedule_changed
= data
.prune_schedule
!= update
.prune_schedule
;
274 data
.prune_schedule
= update
.prune_schedule
;
277 if update
.keep_last
.is_some() { data.keep_last = update.keep_last; }
278 if update
.keep_hourly
.is_some() { data.keep_hourly = update.keep_hourly; }
279 if update
.keep_daily
.is_some() { data.keep_daily = update.keep_daily; }
280 if update
.keep_weekly
.is_some() { data.keep_weekly = update.keep_weekly; }
281 if update
.keep_monthly
.is_some() { data.keep_monthly = update.keep_monthly; }
282 if update
.keep_yearly
.is_some() { data.keep_yearly = update.keep_yearly; }
284 if let Some(notify_str
) = update
.notify
{
285 let value
= parse_property_string(¬ify_str
, &DatastoreNotify
::API_SCHEMA
)?
;
286 let notify
: DatastoreNotify
= serde_json
::from_value(value
)?
;
287 if let DatastoreNotify { gc: None, verify: None, sync: None }
= notify
{
290 data
.notify
= Some(notify_str
);
293 if update
.verify_new
.is_some() { data.verify_new = update.verify_new; }
295 if update
.notify_user
.is_some() { data.notify_user = update.notify_user; }
297 config
.set_data(&name
, "datastore", &data
)?
;
299 pbs_config
::datastore
::save_config(&config
)?
;
301 // we want to reset the statefiles, to avoid an immediate action in some cases
302 // (e.g. going from monthly to weekly in the second week of the month)
303 if gc_schedule_changed
{
304 jobstate
::update_job_last_run_time("garbage_collection", &name
)?
;
307 if prune_schedule_changed
{
308 jobstate
::update_job_last_run_time("prune", &name
)?
;
319 schema
: DATASTORE_SCHEMA
,
321 "keep-job-configs": {
322 description
: "If enabled, the job configurations related to this datastore will be kept.",
329 schema
: PROXMOX_CONFIG_DIGEST_SCHEMA
,
334 permission
: &Permission
::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_ALLOCATE
, false),
337 /// Remove a datastore configuration.
338 pub async
fn delete_datastore(
340 keep_job_configs
: bool
,
341 digest
: Option
<String
>,
342 rpcenv
: &mut dyn RpcEnvironment
,
343 ) -> Result
<(), Error
> {
345 let _lock
= pbs_config
::datastore
::lock_config()?
;
347 let (mut config
, expected_digest
) = pbs_config
::datastore
::config()?
;
349 if let Some(ref digest
) = digest
{
350 let digest
= proxmox
::tools
::hex_to_digest(digest
)?
;
351 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
354 match config
.sections
.get(&name
) {
355 Some(_
) => { config.sections.remove(&name); }
,
356 None
=> bail
!("datastore '{}' does not exist.", name
),
359 if !keep_job_configs
{
360 for job
in list_verification_jobs(Some(name
.clone()), Value
::Null
, rpcenv
)?
{
361 delete_verification_job(job
.config
.id
, None
, rpcenv
)?
363 for job
in list_sync_jobs(Some(name
.clone()), Value
::Null
, rpcenv
)?
{
364 delete_sync_job(job
.config
.id
, None
, rpcenv
)?
367 let tape_jobs
= list_tape_backup_jobs(Value
::Null
, rpcenv
)?
;
368 for job_config
in tape_jobs
.into_iter().filter(|config
| config
.setup
.store
== name
) {
369 delete_tape_backup_job(job_config
.id
, None
, rpcenv
)?
;
373 pbs_config
::datastore
::save_config(&config
)?
;
376 let _
= jobstate
::remove_state_file("prune", &name
);
377 let _
= jobstate
::remove_state_file("garbage_collection", &name
);
379 crate::server
::notify_datastore_removed().await?
;
384 const ITEM_ROUTER
: Router
= Router
::new()
385 .get(&API_METHOD_READ_DATASTORE
)
386 .put(&API_METHOD_UPDATE_DATASTORE
)
387 .delete(&API_METHOD_DELETE_DATASTORE
);
389 pub const ROUTER
: Router
= Router
::new()
390 .get(&API_METHOD_LIST_DATASTORES
)
391 .post(&API_METHOD_CREATE_DATASTORE
)
392 .match_all("name", &ITEM_ROUTER
);