1 use std
::path
::PathBuf
;
3 use ::serde
::{Deserialize, Serialize}
;
8 use proxmox_router
::{http_bail, Permission, Router, RpcEnvironment, RpcEnvironmentType}
;
9 use proxmox_schema
::{api, param_bail, ApiType}
;
10 use proxmox_section_config
::SectionConfigData
;
11 use proxmox_sys
::{task_warn, WorkerTaskContext}
;
14 Authid
, DataStoreConfig
, DataStoreConfigUpdater
, DatastoreNotify
, DatastoreTuning
,
15 DATASTORE_SCHEMA
, PRIV_DATASTORE_ALLOCATE
, PRIV_DATASTORE_AUDIT
, PRIV_DATASTORE_MODIFY
,
16 PROXMOX_CONFIG_DIGEST_SCHEMA
, UPID_SCHEMA
,
18 use pbs_config
::BackupLockGuard
;
19 use pbs_datastore
::chunk_store
::ChunkStore
;
21 use crate::api2
::admin
::{
22 prune
::list_prune_jobs
, sync
::list_sync_jobs
, verify
::list_verification_jobs
,
24 use crate::api2
::config
::prune
::delete_prune_job
;
25 use crate::api2
::config
::sync
::delete_sync_job
;
26 use crate::api2
::config
::tape_backup_job
::{delete_tape_backup_job, list_tape_backup_jobs}
;
27 use crate::api2
::config
::verify
::delete_verification_job
;
28 use pbs_config
::CachedUserInfo
;
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 rpcenv
: &mut dyn RpcEnvironment
,
51 ) -> 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"] = hex
::encode(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 WorkerTaskContext
>,
73 ) -> Result
<(), Error
> {
74 let path
: PathBuf
= datastore
.path
.clone().into();
76 let tuning
: DatastoreTuning
= serde_json
::from_value(
77 DatastoreTuning
::API_SCHEMA
78 .parse_property_string(datastore
.tuning
.as_deref().unwrap_or(""))?
,
80 let backup_user
= pbs_config
::backup_user()?
;
81 let _store
= ChunkStore
::create(
87 tuning
.sync_level
.unwrap_or_default(),
90 config
.set_data(&datastore
.name
, "datastore", &datastore
)?
;
92 pbs_config
::datastore
::save_config(&config
)?
;
94 jobstate
::create_state_file("prune", &datastore
.name
)?
;
95 jobstate
::create_state_file("garbage_collection", &datastore
.name
)?
;
105 type: DataStoreConfig
,
111 permission
: &Permission
::Privilege(&["datastore"], PRIV_DATASTORE_ALLOCATE
, false),
114 /// Create new datastore config.
115 pub fn create_datastore(
116 config
: DataStoreConfig
,
117 rpcenv
: &mut dyn RpcEnvironment
,
118 ) -> Result
<String
, Error
> {
119 let lock
= pbs_config
::datastore
::lock_config()?
;
121 let (section_config
, _digest
) = pbs_config
::datastore
::config()?
;
123 if section_config
.sections
.get(&config
.name
).is_some() {
124 param_bail
!("name", "datastore '{}' already exists.", config
.name
);
127 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
128 let to_stdout
= rpcenv
.env_type() == RpcEnvironmentType
::CLI
;
130 WorkerTask
::new_thread(
132 Some(config
.name
.to_string()),
135 move |worker
| do_create_datastore(lock
, section_config
, config
, Some(&worker
)),
143 schema
: DATASTORE_SCHEMA
,
147 returns
: { type: DataStoreConfig }
,
149 permission
: &Permission
::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_AUDIT
, false),
152 /// Read a datastore configuration.
153 pub fn read_datastore(
155 rpcenv
: &mut dyn RpcEnvironment
,
156 ) -> Result
<DataStoreConfig
, Error
> {
157 let (config
, digest
) = pbs_config
::datastore
::config()?
;
159 let store_config
= config
.lookup("datastore", &name
)?
;
160 rpcenv
["digest"] = hex
::encode(digest
).into();
166 #[derive(Serialize, Deserialize)]
167 #[serde(rename_all = "kebab-case")]
168 /// Deletable property name
169 pub enum DeletableProperty
{
170 /// Delete the comment property.
172 /// Delete the garbage collection schedule.
174 /// Delete the prune job schedule.
176 /// Delete the keep-last property
178 /// Delete the keep-hourly property
180 /// Delete the keep-daily property
182 /// Delete the keep-weekly property
184 /// Delete the keep-monthly property
186 /// Delete the keep-yearly property
188 /// Delete the verify-new property
190 /// Delete the notify-user property
192 /// Delete the notify property
194 /// Delete the tuning property
196 /// Delete the maintenance-mode property
205 schema
: DATASTORE_SCHEMA
,
208 type: DataStoreConfigUpdater
,
212 description
: "List of properties to delete.",
216 type: DeletableProperty
,
221 schema
: PROXMOX_CONFIG_DIGEST_SCHEMA
,
226 permission
: &Permission
::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_MODIFY
, false),
229 /// Update datastore config.
230 pub fn update_datastore(
231 update
: DataStoreConfigUpdater
,
233 delete
: Option
<Vec
<DeletableProperty
>>,
234 digest
: Option
<String
>,
235 ) -> Result
<(), Error
> {
236 let _lock
= pbs_config
::datastore
::lock_config()?
;
238 // pass/compare digest
239 let (mut config
, expected_digest
) = pbs_config
::datastore
::config()?
;
241 if let Some(ref digest
) = digest
{
242 let digest
= <[u8; 32]>::from_hex(digest
)?
;
243 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
246 let mut data
: DataStoreConfig
= config
.lookup("datastore", &name
)?
;
248 if let Some(delete
) = delete
{
249 for delete_prop
in delete
{
251 DeletableProperty
::Comment
=> {
254 DeletableProperty
::GcSchedule
=> {
255 data
.gc_schedule
= None
;
257 DeletableProperty
::PruneSchedule
=> {
258 data
.prune_schedule
= None
;
260 DeletableProperty
::KeepLast
=> {
261 data
.keep
.keep_last
= None
;
263 DeletableProperty
::KeepHourly
=> {
264 data
.keep
.keep_hourly
= None
;
266 DeletableProperty
::KeepDaily
=> {
267 data
.keep
.keep_daily
= None
;
269 DeletableProperty
::KeepWeekly
=> {
270 data
.keep
.keep_weekly
= None
;
272 DeletableProperty
::KeepMonthly
=> {
273 data
.keep
.keep_monthly
= None
;
275 DeletableProperty
::KeepYearly
=> {
276 data
.keep
.keep_yearly
= None
;
278 DeletableProperty
::VerifyNew
=> {
279 data
.verify_new
= None
;
281 DeletableProperty
::Notify
=> {
284 DeletableProperty
::NotifyUser
=> {
285 data
.notify_user
= None
;
287 DeletableProperty
::Tuning
=> {
290 DeletableProperty
::MaintenanceMode
=> {
291 data
.maintenance_mode
= None
;
297 if let Some(comment
) = update
.comment
{
298 let comment
= comment
.trim().to_string();
299 if comment
.is_empty() {
302 data
.comment
= Some(comment
);
306 let mut gc_schedule_changed
= false;
307 if update
.gc_schedule
.is_some() {
308 gc_schedule_changed
= data
.gc_schedule
!= update
.gc_schedule
;
309 data
.gc_schedule
= update
.gc_schedule
;
312 macro_rules
! prune_disabled
{
313 ($
(($param
:literal
, $
($member
:tt
)+)),+) => {
315 if update
.$
($member
)+.is_some() {
318 "datastore prune settings have been replaced by prune jobs",
325 ("keep-last", keep
.keep_last
),
326 ("keep-hourly", keep
.keep_hourly
),
327 ("keep-daily", keep
.keep_daily
),
328 ("keep-weekly", keep
.keep_weekly
),
329 ("keep-monthly", keep
.keep_monthly
),
330 ("keep-yearly", keep
.keep_yearly
),
331 ("prune-schedule", prune_schedule
)
334 if let Some(notify_str
) = update
.notify
{
335 let value
= DatastoreNotify
::API_SCHEMA
.parse_property_string(¬ify_str
)?
;
336 let notify
: DatastoreNotify
= serde_json
::from_value(value
)?
;
337 if let DatastoreNotify
{
346 data
.notify
= Some(notify_str
);
349 if update
.verify_new
.is_some() {
350 data
.verify_new
= update
.verify_new
;
353 if update
.notify_user
.is_some() {
354 data
.notify_user
= update
.notify_user
;
357 if update
.tuning
.is_some() {
358 data
.tuning
= update
.tuning
;
361 if update
.maintenance_mode
.is_some() {
362 data
.maintenance_mode
= update
.maintenance_mode
;
365 config
.set_data(&name
, "datastore", &data
)?
;
367 pbs_config
::datastore
::save_config(&config
)?
;
369 // we want to reset the statefiles, to avoid an immediate action in some cases
370 // (e.g. going from monthly to weekly in the second week of the month)
371 if gc_schedule_changed
{
372 jobstate
::update_job_last_run_time("garbage_collection", &name
)?
;
383 schema
: DATASTORE_SCHEMA
,
385 "keep-job-configs": {
386 description
: "If enabled, the job configurations related to this datastore will be kept.",
392 description
: "Delete the datastore's underlying contents",
399 schema
: PROXMOX_CONFIG_DIGEST_SCHEMA
,
404 permission
: &Permission
::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_ALLOCATE
, false),
410 /// Remove a datastore configuration and optionally delete all its contents.
411 pub async
fn delete_datastore(
413 keep_job_configs
: bool
,
415 digest
: Option
<String
>,
416 rpcenv
: &mut dyn RpcEnvironment
,
417 ) -> Result
<String
, Error
> {
418 let _lock
= pbs_config
::datastore
::lock_config()?
;
420 let (config
, expected_digest
) = pbs_config
::datastore
::config()?
;
422 if let Some(ref digest
) = digest
{
423 let digest
= <[u8; 32]>::from_hex(digest
)?
;
424 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
427 if !config
.sections
.contains_key(&name
) {
428 http_bail
!(NOT_FOUND
, "datastore '{}' does not exist.", name
);
431 if !keep_job_configs
{
432 for job
in list_verification_jobs(Some(name
.clone()), Value
::Null
, rpcenv
)?
{
433 delete_verification_job(job
.config
.id
, None
, rpcenv
)?
435 for job
in list_sync_jobs(Some(name
.clone()), Value
::Null
, rpcenv
)?
{
436 delete_sync_job(job
.config
.id
, None
, rpcenv
)?
438 for job
in list_prune_jobs(Some(name
.clone()), Value
::Null
, rpcenv
)?
{
439 delete_prune_job(job
.config
.id
, None
, rpcenv
)?
442 let (mut tree
, _digest
) = pbs_config
::acl
::config()?
;
443 tree
.delete_node(&format
!("/datastore/{}", name
));
444 pbs_config
::acl
::save_config(&tree
)?
;
446 let tape_jobs
= list_tape_backup_jobs(Value
::Null
, rpcenv
)?
;
447 for job_config
in tape_jobs
449 .filter(|config
| config
.setup
.store
== name
)
451 delete_tape_backup_job(job_config
.id
, None
, rpcenv
)?
;
455 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
456 let to_stdout
= rpcenv
.env_type() == RpcEnvironmentType
::CLI
;
458 let upid
= WorkerTask
::new_thread(
464 pbs_datastore
::DataStore
::destroy(&name
, destroy_data
, &worker
)?
;
467 let _
= jobstate
::remove_state_file("prune", &name
);
468 let _
= jobstate
::remove_state_file("garbage_collection", &name
);
471 proxmox_async
::runtime
::block_on(crate::server
::notify_datastore_removed())
473 task_warn
!(worker
, "failed to notify after datastore removal: {err}");
483 const ITEM_ROUTER
: Router
= Router
::new()
484 .get(&API_METHOD_READ_DATASTORE
)
485 .put(&API_METHOD_UPDATE_DATASTORE
)
486 .delete(&API_METHOD_DELETE_DATASTORE
);
488 pub const ROUTER
: Router
= Router
::new()
489 .get(&API_METHOD_LIST_DATASTORES
)
490 .post(&API_METHOD_CREATE_DATASTORE
)
491 .match_all("name", &ITEM_ROUTER
);