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
::WorkerTaskContext
;
14 Authid
, DataStoreConfig
, DataStoreConfigUpdater
, DatastoreNotify
, DatastoreTuning
,
15 DATASTORE_SCHEMA
, PRIV_DATASTORE_ALLOCATE
, PRIV_DATASTORE_AUDIT
, PRIV_DATASTORE_MODIFY
,
16 PROXMOX_CONFIG_DIGEST_SCHEMA
,
18 use pbs_config
::BackupLockGuard
;
19 use pbs_datastore
::chunk_store
::ChunkStore
;
21 use crate::api2
::admin
::{sync::list_sync_jobs, verify::list_verification_jobs}
;
22 use crate::api2
::config
::sync
::delete_sync_job
;
23 use crate::api2
::config
::tape_backup_job
::{delete_tape_backup_job, list_tape_backup_jobs}
;
24 use crate::api2
::config
::verify
::delete_verification_job
;
25 use pbs_config
::CachedUserInfo
;
27 use proxmox_rest_server
::WorkerTask
;
29 use crate::server
::jobstate
;
36 description
: "List the configured datastores (with config digest).",
38 items
: { type: DataStoreConfig }
,
41 permission
: &Permission
::Anybody
,
44 /// List all datastores
45 pub fn list_datastores(
47 rpcenv
: &mut dyn RpcEnvironment
,
48 ) -> Result
<Vec
<DataStoreConfig
>, Error
> {
49 let (config
, digest
) = pbs_config
::datastore
::config()?
;
51 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
52 let user_info
= CachedUserInfo
::new()?
;
54 rpcenv
["digest"] = hex
::encode(&digest
).into();
56 let list
: Vec
<DataStoreConfig
> = config
.convert_to_typed_array("datastore")?
;
57 let filter_by_privs
= |store
: &DataStoreConfig
| {
58 let user_privs
= user_info
.lookup_privs(&auth_id
, &["datastore", &store
.name
]);
59 (user_privs
& PRIV_DATASTORE_AUDIT
) != 0
62 Ok(list
.into_iter().filter(filter_by_privs
).collect())
65 pub(crate) fn do_create_datastore(
66 _lock
: BackupLockGuard
,
67 mut config
: SectionConfigData
,
68 datastore
: DataStoreConfig
,
69 worker
: Option
<&dyn WorkerTaskContext
>,
70 ) -> Result
<(), Error
> {
71 let path
: PathBuf
= datastore
.path
.clone().into();
73 let tuning
: DatastoreTuning
= serde_json
::from_value(
74 DatastoreTuning
::API_SCHEMA
75 .parse_property_string(datastore
.tuning
.as_deref().unwrap_or(""))?
,
77 let backup_user
= pbs_config
::backup_user()?
;
78 let _store
= ChunkStore
::create(
84 tuning
.sync_level
.unwrap_or_default(),
87 config
.set_data(&datastore
.name
, "datastore", &datastore
)?
;
89 pbs_config
::datastore
::save_config(&config
)?
;
91 jobstate
::create_state_file("prune", &datastore
.name
)?
;
92 jobstate
::create_state_file("garbage_collection", &datastore
.name
)?
;
102 type: DataStoreConfig
,
108 permission
: &Permission
::Privilege(&["datastore"], PRIV_DATASTORE_ALLOCATE
, false),
111 /// Create new datastore config.
112 pub fn create_datastore(
113 config
: DataStoreConfig
,
114 rpcenv
: &mut dyn RpcEnvironment
,
115 ) -> Result
<String
, Error
> {
116 let lock
= pbs_config
::datastore
::lock_config()?
;
118 let (section_config
, _digest
) = pbs_config
::datastore
::config()?
;
120 if section_config
.sections
.get(&config
.name
).is_some() {
121 param_bail
!("name", "datastore '{}' already exists.", config
.name
);
124 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
125 let to_stdout
= rpcenv
.env_type() == RpcEnvironmentType
::CLI
;
127 WorkerTask
::new_thread(
129 Some(config
.name
.to_string()),
132 move |worker
| do_create_datastore(lock
, section_config
, config
, Some(&worker
)),
140 schema
: DATASTORE_SCHEMA
,
144 returns
: { type: DataStoreConfig }
,
146 permission
: &Permission
::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_AUDIT
, false),
149 /// Read a datastore configuration.
150 pub fn read_datastore(
152 rpcenv
: &mut dyn RpcEnvironment
,
153 ) -> Result
<DataStoreConfig
, Error
> {
154 let (config
, digest
) = pbs_config
::datastore
::config()?
;
156 let store_config
= config
.lookup("datastore", &name
)?
;
157 rpcenv
["digest"] = hex
::encode(&digest
).into();
163 #[derive(Serialize, Deserialize)]
164 #[serde(rename_all = "kebab-case")]
165 #[allow(non_camel_case_types)]
166 /// Deletable property name
167 pub enum DeletableProperty
{
168 /// Delete the comment property.
170 /// Delete the garbage collection schedule.
172 /// Delete the prune job schedule.
174 /// Delete the keep-last property
176 /// Delete the keep-hourly property
178 /// Delete the keep-daily property
180 /// Delete the keep-weekly property
182 /// Delete the keep-monthly property
184 /// Delete the keep-yearly property
186 /// Delete the verify-new property
188 /// Delete the notify-user property
190 /// Delete the notify property
192 /// Delete the tuning property
194 /// Delete the maintenance-mode property
203 schema
: DATASTORE_SCHEMA
,
206 type: DataStoreConfigUpdater
,
210 description
: "List of properties to delete.",
214 type: DeletableProperty
,
219 schema
: PROXMOX_CONFIG_DIGEST_SCHEMA
,
224 permission
: &Permission
::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_MODIFY
, false),
227 /// Update datastore config.
228 pub fn update_datastore(
229 update
: DataStoreConfigUpdater
,
231 delete
: Option
<Vec
<DeletableProperty
>>,
232 digest
: Option
<String
>,
233 ) -> Result
<(), Error
> {
234 let _lock
= pbs_config
::datastore
::lock_config()?
;
236 // pass/compare digest
237 let (mut config
, expected_digest
) = pbs_config
::datastore
::config()?
;
239 if let Some(ref digest
) = digest
{
240 let digest
= <[u8; 32]>::from_hex(digest
)?
;
241 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
244 let mut data
: DataStoreConfig
= config
.lookup("datastore", &name
)?
;
246 if let Some(delete
) = delete
{
247 for delete_prop
in delete
{
249 DeletableProperty
::comment
=> {
252 DeletableProperty
::gc_schedule
=> {
253 data
.gc_schedule
= None
;
255 DeletableProperty
::prune_schedule
=> {
256 data
.prune_schedule
= None
;
258 DeletableProperty
::keep_last
=> {
259 data
.keep
.keep_last
= None
;
261 DeletableProperty
::keep_hourly
=> {
262 data
.keep
.keep_hourly
= None
;
264 DeletableProperty
::keep_daily
=> {
265 data
.keep
.keep_daily
= None
;
267 DeletableProperty
::keep_weekly
=> {
268 data
.keep
.keep_weekly
= None
;
270 DeletableProperty
::keep_monthly
=> {
271 data
.keep
.keep_monthly
= None
;
273 DeletableProperty
::keep_yearly
=> {
274 data
.keep
.keep_yearly
= None
;
276 DeletableProperty
::verify_new
=> {
277 data
.verify_new
= None
;
279 DeletableProperty
::notify
=> {
282 DeletableProperty
::notify_user
=> {
283 data
.notify_user
= None
;
285 DeletableProperty
::tuning
=> {
288 DeletableProperty
::maintenance_mode
=> {
289 data
.maintenance_mode
= None
;
295 if let Some(comment
) = update
.comment
{
296 let comment
= comment
.trim().to_string();
297 if comment
.is_empty() {
300 data
.comment
= Some(comment
);
304 let mut gc_schedule_changed
= false;
305 if update
.gc_schedule
.is_some() {
306 gc_schedule_changed
= data
.gc_schedule
!= update
.gc_schedule
;
307 data
.gc_schedule
= update
.gc_schedule
;
310 macro_rules
! prune_disabled
{
311 ($
(($param
:literal
, $
($member
:tt
)+)),+) => {
313 if update
.$
($member
)+.is_some() {
316 "datastore prune settings have been replaced by prune jobs",
323 ("keep-last", keep
.keep_last
),
324 ("keep-hourly", keep
.keep_hourly
),
325 ("keep-daily", keep
.keep_daily
),
326 ("keep-weekly", keep
.keep_weekly
),
327 ("keep-monthly", keep
.keep_monthly
),
328 ("keep-yearly", keep
.keep_yearly
),
329 ("prune-schedule", prune_schedule
)
332 if let Some(notify_str
) = update
.notify
{
333 let value
= DatastoreNotify
::API_SCHEMA
.parse_property_string(¬ify_str
)?
;
334 let notify
: DatastoreNotify
= serde_json
::from_value(value
)?
;
335 if let DatastoreNotify
{
344 data
.notify
= Some(notify_str
);
347 if update
.verify_new
.is_some() {
348 data
.verify_new
= update
.verify_new
;
351 if update
.notify_user
.is_some() {
352 data
.notify_user
= update
.notify_user
;
355 if update
.tuning
.is_some() {
356 data
.tuning
= update
.tuning
;
359 if update
.maintenance_mode
.is_some() {
360 data
.maintenance_mode
= update
.maintenance_mode
;
363 config
.set_data(&name
, "datastore", &data
)?
;
365 pbs_config
::datastore
::save_config(&config
)?
;
367 // we want to reset the statefiles, to avoid an immediate action in some cases
368 // (e.g. going from monthly to weekly in the second week of the month)
369 if gc_schedule_changed
{
370 jobstate
::update_job_last_run_time("garbage_collection", &name
)?
;
381 schema
: DATASTORE_SCHEMA
,
383 "keep-job-configs": {
384 description
: "If enabled, the job configurations related to this datastore will be kept.",
391 schema
: PROXMOX_CONFIG_DIGEST_SCHEMA
,
396 permission
: &Permission
::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_ALLOCATE
, false),
399 /// Remove a datastore configuration.
400 pub async
fn delete_datastore(
402 keep_job_configs
: bool
,
403 digest
: Option
<String
>,
404 rpcenv
: &mut dyn RpcEnvironment
,
405 ) -> Result
<(), Error
> {
406 let _lock
= pbs_config
::datastore
::lock_config()?
;
408 let (mut config
, expected_digest
) = pbs_config
::datastore
::config()?
;
410 if let Some(ref digest
) = digest
{
411 let digest
= <[u8; 32]>::from_hex(digest
)?
;
412 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
415 match config
.sections
.get(&name
) {
417 config
.sections
.remove(&name
);
419 None
=> http_bail
!(NOT_FOUND
, "datastore '{}' does not exist.", name
),
422 if !keep_job_configs
{
423 for job
in list_verification_jobs(Some(name
.clone()), Value
::Null
, rpcenv
)?
{
424 delete_verification_job(job
.config
.id
, None
, rpcenv
)?
426 for job
in list_sync_jobs(Some(name
.clone()), Value
::Null
, rpcenv
)?
{
427 delete_sync_job(job
.config
.id
, None
, rpcenv
)?
430 let tape_jobs
= list_tape_backup_jobs(Value
::Null
, rpcenv
)?
;
431 for job_config
in tape_jobs
433 .filter(|config
| config
.setup
.store
== name
)
435 delete_tape_backup_job(job_config
.id
, None
, rpcenv
)?
;
439 pbs_config
::datastore
::save_config(&config
)?
;
442 let _
= jobstate
::remove_state_file("prune", &name
);
443 let _
= jobstate
::remove_state_file("garbage_collection", &name
);
445 crate::server
::notify_datastore_removed().await?
;
450 const ITEM_ROUTER
: Router
= Router
::new()
451 .get(&API_METHOD_READ_DATASTORE
)
452 .put(&API_METHOD_UPDATE_DATASTORE
)
453 .delete(&API_METHOD_DELETE_DATASTORE
);
455 pub const ROUTER
: Router
= Router
::new()
456 .get(&API_METHOD_LIST_DATASTORES
)
457 .post(&API_METHOD_CREATE_DATASTORE
)
458 .match_all("name", &ITEM_ROUTER
);