1 use std
::path
::PathBuf
;
3 use anyhow
::{bail, Error}
;
5 use ::serde
::{Deserialize, Serialize}
;
7 use proxmox
::api
::{api, Router, RpcEnvironment, RpcEnvironmentType, Permission}
;
8 use proxmox
::api
::section_config
::SectionConfigData
;
9 use proxmox
::api
::schema
::{ApiType, parse_property_string}
;
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
,
19 use pbs_tools
::task
::TaskState
;
21 use crate::api2
::config
::sync
::delete_sync_job
;
22 use crate::api2
::config
::verify
::delete_verification_job
;
23 use crate::api2
::config
::tape_backup_job
::{list_tape_backup_jobs, delete_tape_backup_job}
;
24 use crate::api2
::admin
::{
26 verify
::list_verification_jobs
,
28 use pbs_config
::CachedUserInfo
;
29 use proxmox_rest_server
::WorkerTask
;
31 use crate::server
::jobstate
;
38 description
: "List the configured datastores (with config digest).",
40 items
: { type: DataStoreConfig }
,
43 permission
: &Permission
::Anybody
,
46 /// List all datastores
47 pub fn list_datastores(
49 mut rpcenv
: &mut dyn RpcEnvironment
,
50 ) -> Result
<Vec
<DataStoreConfig
>, Error
> {
52 let (config
, digest
) = pbs_config
::datastore
::config()?
;
54 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
55 let user_info
= CachedUserInfo
::new()?
;
57 rpcenv
["digest"] = proxmox
::tools
::digest_to_hex(&digest
).into();
59 let list
:Vec
<DataStoreConfig
> = config
.convert_to_typed_array("datastore")?
;
60 let filter_by_privs
= |store
: &DataStoreConfig
| {
61 let user_privs
= user_info
.lookup_privs(&auth_id
, &["datastore", &store
.name
]);
62 (user_privs
& PRIV_DATASTORE_AUDIT
) != 0
65 Ok(list
.into_iter().filter(filter_by_privs
).collect())
68 pub(crate) fn do_create_datastore(
69 _lock
: BackupLockGuard
,
70 mut config
: SectionConfigData
,
71 datastore
: DataStoreConfig
,
72 worker
: Option
<&dyn TaskState
>,
73 ) -> Result
<(), Error
> {
74 let path
: PathBuf
= datastore
.path
.clone().into();
76 let backup_user
= pbs_config
::backup_user()?
;
77 let _store
= ChunkStore
::create(&datastore
.name
, path
, backup_user
.uid
, backup_user
.gid
, worker
)?
;
79 config
.set_data(&datastore
.name
, "datastore", &datastore
)?
;
81 pbs_config
::datastore
::save_config(&config
)?
;
83 jobstate
::create_state_file("prune", &datastore
.name
)?
;
84 jobstate
::create_state_file("garbage_collection", &datastore
.name
)?
;
94 type: DataStoreConfig
,
100 permission
: &Permission
::Privilege(&["datastore"], PRIV_DATASTORE_ALLOCATE
, false),
103 /// Create new datastore config.
104 pub fn create_datastore(
105 config
: DataStoreConfig
,
106 rpcenv
: &mut dyn RpcEnvironment
,
107 ) -> Result
<String
, Error
> {
109 let lock
= pbs_config
::datastore
::lock_config()?
;
111 let (section_config
, _digest
) = pbs_config
::datastore
::config()?
;
113 if section_config
.sections
.get(&config
.name
).is_some() {
114 bail
!("datastore '{}' already exists.", config
.name
);
117 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
118 let to_stdout
= rpcenv
.env_type() == RpcEnvironmentType
::CLI
;
120 WorkerTask
::new_thread(
122 Some(config
.name
.to_string()),
125 move |worker
| do_create_datastore(lock
, section_config
, config
, Some(&worker
)),
133 schema
: DATASTORE_SCHEMA
,
137 returns
: { type: DataStoreConfig }
,
139 permission
: &Permission
::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_AUDIT
, false),
142 /// Read a datastore configuration.
143 pub fn read_datastore(
145 mut rpcenv
: &mut dyn RpcEnvironment
,
146 ) -> Result
<DataStoreConfig
, Error
> {
147 let (config
, digest
) = pbs_config
::datastore
::config()?
;
149 let store_config
= config
.lookup("datastore", &name
)?
;
150 rpcenv
["digest"] = proxmox
::tools
::digest_to_hex(&digest
).into();
156 #[derive(Serialize, Deserialize)]
157 #[serde(rename_all="kebab-case")]
158 #[allow(non_camel_case_types)]
159 /// Deletable property name
160 pub enum DeletableProperty
{
161 /// Delete the comment property.
163 /// Delete the garbage collection schedule.
165 /// Delete the prune job schedule.
167 /// Delete the keep-last property
169 /// Delete the keep-hourly property
171 /// Delete the keep-daily property
173 /// Delete the keep-weekly property
175 /// Delete the keep-monthly property
177 /// Delete the keep-yearly property
179 /// Delete the verify-new property
181 /// Delete the notify-user property
183 /// Delete the notify property
192 schema
: DATASTORE_SCHEMA
,
195 type: DataStoreConfigUpdater
,
199 description
: "List of properties to delete.",
203 type: DeletableProperty
,
208 schema
: PROXMOX_CONFIG_DIGEST_SCHEMA
,
213 permission
: &Permission
::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_MODIFY
, false),
216 /// Update datastore config.
217 pub fn update_datastore(
218 update
: DataStoreConfigUpdater
,
220 delete
: Option
<Vec
<DeletableProperty
>>,
221 digest
: Option
<String
>,
222 ) -> Result
<(), Error
> {
224 let _lock
= pbs_config
::datastore
::lock_config()?
;
226 // pass/compare digest
227 let (mut config
, expected_digest
) = pbs_config
::datastore
::config()?
;
229 if let Some(ref digest
) = digest
{
230 let digest
= proxmox
::tools
::hex_to_digest(digest
)?
;
231 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
234 let mut data
: DataStoreConfig
= config
.lookup("datastore", &name
)?
;
236 if let Some(delete
) = delete
{
237 for delete_prop
in delete
{
239 DeletableProperty
::comment
=> { data.comment = None; }
,
240 DeletableProperty
::gc_schedule
=> { data.gc_schedule = None; }
,
241 DeletableProperty
::prune_schedule
=> { data.prune_schedule = None; }
,
242 DeletableProperty
::keep_last
=> { data.keep_last = None; }
,
243 DeletableProperty
::keep_hourly
=> { data.keep_hourly = None; }
,
244 DeletableProperty
::keep_daily
=> { data.keep_daily = None; }
,
245 DeletableProperty
::keep_weekly
=> { data.keep_weekly = None; }
,
246 DeletableProperty
::keep_monthly
=> { data.keep_monthly = None; }
,
247 DeletableProperty
::keep_yearly
=> { data.keep_yearly = None; }
,
248 DeletableProperty
::verify_new
=> { data.verify_new = None; }
,
249 DeletableProperty
::notify
=> { data.notify = None; }
,
250 DeletableProperty
::notify_user
=> { data.notify_user = None; }
,
255 if let Some(comment
) = update
.comment
{
256 let comment
= comment
.trim().to_string();
257 if comment
.is_empty() {
260 data
.comment
= Some(comment
);
264 let mut gc_schedule_changed
= false;
265 if update
.gc_schedule
.is_some() {
266 gc_schedule_changed
= data
.gc_schedule
!= update
.gc_schedule
;
267 data
.gc_schedule
= update
.gc_schedule
;
270 let mut prune_schedule_changed
= false;
271 if update
.prune_schedule
.is_some() {
272 prune_schedule_changed
= data
.prune_schedule
!= update
.prune_schedule
;
273 data
.prune_schedule
= update
.prune_schedule
;
276 if update
.keep_last
.is_some() { data.keep_last = update.keep_last; }
277 if update
.keep_hourly
.is_some() { data.keep_hourly = update.keep_hourly; }
278 if update
.keep_daily
.is_some() { data.keep_daily = update.keep_daily; }
279 if update
.keep_weekly
.is_some() { data.keep_weekly = update.keep_weekly; }
280 if update
.keep_monthly
.is_some() { data.keep_monthly = update.keep_monthly; }
281 if update
.keep_yearly
.is_some() { data.keep_yearly = update.keep_yearly; }
283 if let Some(notify_str
) = update
.notify
{
284 let value
= parse_property_string(¬ify_str
, &DatastoreNotify
::API_SCHEMA
)?
;
285 let notify
: DatastoreNotify
= serde_json
::from_value(value
)?
;
286 if let DatastoreNotify { gc: None, verify: None, sync: None }
= notify
{
289 data
.notify
= Some(notify_str
);
292 if update
.verify_new
.is_some() { data.verify_new = update.verify_new; }
294 if update
.notify_user
.is_some() { data.notify_user = update.notify_user; }
296 config
.set_data(&name
, "datastore", &data
)?
;
298 pbs_config
::datastore
::save_config(&config
)?
;
300 // we want to reset the statefiles, to avoid an immediate action in some cases
301 // (e.g. going from monthly to weekly in the second week of the month)
302 if gc_schedule_changed
{
303 jobstate
::update_job_last_run_time("garbage_collection", &name
)?
;
306 if prune_schedule_changed
{
307 jobstate
::update_job_last_run_time("prune", &name
)?
;
318 schema
: DATASTORE_SCHEMA
,
320 "keep-job-configs": {
321 description
: "If enabled, the job configurations related to this datastore will be kept.",
328 schema
: PROXMOX_CONFIG_DIGEST_SCHEMA
,
333 permission
: &Permission
::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_ALLOCATE
, false),
336 /// Remove a datastore configuration.
337 pub async
fn delete_datastore(
339 keep_job_configs
: bool
,
340 digest
: Option
<String
>,
341 rpcenv
: &mut dyn RpcEnvironment
,
342 ) -> Result
<(), Error
> {
344 let _lock
= pbs_config
::datastore
::lock_config()?
;
346 let (mut config
, expected_digest
) = pbs_config
::datastore
::config()?
;
348 if let Some(ref digest
) = digest
{
349 let digest
= proxmox
::tools
::hex_to_digest(digest
)?
;
350 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
353 match config
.sections
.get(&name
) {
354 Some(_
) => { config.sections.remove(&name); }
,
355 None
=> bail
!("datastore '{}' does not exist.", name
),
358 if !keep_job_configs
{
359 for job
in list_verification_jobs(Some(name
.clone()), Value
::Null
, rpcenv
)?
{
360 delete_verification_job(job
.config
.id
, None
, rpcenv
)?
362 for job
in list_sync_jobs(Some(name
.clone()), Value
::Null
, rpcenv
)?
{
363 delete_sync_job(job
.config
.id
, None
, rpcenv
)?
366 let tape_jobs
= list_tape_backup_jobs(Value
::Null
, rpcenv
)?
;
367 for job_config
in tape_jobs
.into_iter().filter(|config
| config
.setup
.store
== name
) {
368 delete_tape_backup_job(job_config
.id
, None
, rpcenv
)?
;
372 pbs_config
::datastore
::save_config(&config
)?
;
375 let _
= jobstate
::remove_state_file("prune", &name
);
376 let _
= jobstate
::remove_state_file("garbage_collection", &name
);
378 crate::server
::notify_datastore_removed().await?
;
383 const ITEM_ROUTER
: Router
= Router
::new()
384 .get(&API_METHOD_READ_DATASTORE
)
385 .put(&API_METHOD_UPDATE_DATASTORE
)
386 .delete(&API_METHOD_DELETE_DATASTORE
);
388 pub const ROUTER
: Router
= Router
::new()
389 .get(&API_METHOD_LIST_DATASTORES
)
390 .post(&API_METHOD_CREATE_DATASTORE
)
391 .match_all("name", &ITEM_ROUTER
);