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
, DATASTORE_SCHEMA
,
15 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 mut 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 backup_user
= pbs_config
::backup_user()?
;
74 let _store
= ChunkStore
::create(
82 config
.set_data(&datastore
.name
, "datastore", &datastore
)?
;
84 pbs_config
::datastore
::save_config(&config
)?
;
86 jobstate
::create_state_file("prune", &datastore
.name
)?
;
87 jobstate
::create_state_file("garbage_collection", &datastore
.name
)?
;
97 type: DataStoreConfig
,
103 permission
: &Permission
::Privilege(&["datastore"], PRIV_DATASTORE_ALLOCATE
, false),
106 /// Create new datastore config.
107 pub fn create_datastore(
108 config
: DataStoreConfig
,
109 rpcenv
: &mut dyn RpcEnvironment
,
110 ) -> Result
<String
, Error
> {
111 let lock
= pbs_config
::datastore
::lock_config()?
;
113 let (section_config
, _digest
) = pbs_config
::datastore
::config()?
;
115 if section_config
.sections
.get(&config
.name
).is_some() {
116 param_bail
!("name", "datastore '{}' already exists.", config
.name
);
119 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
120 let to_stdout
= rpcenv
.env_type() == RpcEnvironmentType
::CLI
;
122 WorkerTask
::new_thread(
124 Some(config
.name
.to_string()),
127 move |worker
| do_create_datastore(lock
, section_config
, config
, Some(&worker
)),
135 schema
: DATASTORE_SCHEMA
,
139 returns
: { type: DataStoreConfig }
,
141 permission
: &Permission
::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_AUDIT
, false),
144 /// Read a datastore configuration.
145 pub fn read_datastore(
147 mut rpcenv
: &mut dyn RpcEnvironment
,
148 ) -> Result
<DataStoreConfig
, Error
> {
149 let (config
, digest
) = pbs_config
::datastore
::config()?
;
151 let store_config
= config
.lookup("datastore", &name
)?
;
152 rpcenv
["digest"] = hex
::encode(&digest
).into();
158 #[derive(Serialize, Deserialize)]
159 #[serde(rename_all = "kebab-case")]
160 #[allow(non_camel_case_types)]
161 /// Deletable property name
162 pub enum DeletableProperty
{
163 /// Delete the comment property.
165 /// Delete the garbage collection schedule.
167 /// Delete the prune job schedule.
169 /// Delete the keep-last property
171 /// Delete the keep-hourly property
173 /// Delete the keep-daily property
175 /// Delete the keep-weekly property
177 /// Delete the keep-monthly property
179 /// Delete the keep-yearly property
181 /// Delete the verify-new property
183 /// Delete the notify-user property
185 /// Delete the notify property
187 /// Delete the tuning property
189 /// Delete the maintenance-mode property
198 schema
: DATASTORE_SCHEMA
,
201 type: DataStoreConfigUpdater
,
205 description
: "List of properties to delete.",
209 type: DeletableProperty
,
214 schema
: PROXMOX_CONFIG_DIGEST_SCHEMA
,
219 permission
: &Permission
::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_MODIFY
, false),
222 /// Update datastore config.
223 pub fn update_datastore(
224 update
: DataStoreConfigUpdater
,
226 delete
: Option
<Vec
<DeletableProperty
>>,
227 digest
: Option
<String
>,
228 ) -> Result
<(), Error
> {
229 let _lock
= pbs_config
::datastore
::lock_config()?
;
231 // pass/compare digest
232 let (mut config
, expected_digest
) = pbs_config
::datastore
::config()?
;
234 if let Some(ref digest
) = digest
{
235 let digest
= <[u8; 32]>::from_hex(digest
)?
;
236 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
239 let mut data
: DataStoreConfig
= config
.lookup("datastore", &name
)?
;
241 if let Some(delete
) = delete
{
242 for delete_prop
in delete
{
244 DeletableProperty
::comment
=> {
247 DeletableProperty
::gc_schedule
=> {
248 data
.gc_schedule
= None
;
250 DeletableProperty
::prune_schedule
=> {
251 data
.prune_schedule
= None
;
253 DeletableProperty
::keep_last
=> {
254 data
.keep_last
= None
;
256 DeletableProperty
::keep_hourly
=> {
257 data
.keep_hourly
= None
;
259 DeletableProperty
::keep_daily
=> {
260 data
.keep_daily
= None
;
262 DeletableProperty
::keep_weekly
=> {
263 data
.keep_weekly
= None
;
265 DeletableProperty
::keep_monthly
=> {
266 data
.keep_monthly
= None
;
268 DeletableProperty
::keep_yearly
=> {
269 data
.keep_yearly
= None
;
271 DeletableProperty
::verify_new
=> {
272 data
.verify_new
= None
;
274 DeletableProperty
::notify
=> {
277 DeletableProperty
::notify_user
=> {
278 data
.notify_user
= None
;
280 DeletableProperty
::tuning
=> {
283 DeletableProperty
::maintenance_mode
=> {
284 data
.maintenance_mode
= None
;
290 if let Some(comment
) = update
.comment
{
291 let comment
= comment
.trim().to_string();
292 if comment
.is_empty() {
295 data
.comment
= Some(comment
);
299 let mut gc_schedule_changed
= false;
300 if update
.gc_schedule
.is_some() {
301 gc_schedule_changed
= data
.gc_schedule
!= update
.gc_schedule
;
302 data
.gc_schedule
= update
.gc_schedule
;
305 let mut prune_schedule_changed
= false;
306 if update
.prune_schedule
.is_some() {
307 prune_schedule_changed
= data
.prune_schedule
!= update
.prune_schedule
;
308 data
.prune_schedule
= update
.prune_schedule
;
311 if update
.keep_last
.is_some() {
312 data
.keep_last
= update
.keep_last
;
314 if update
.keep_hourly
.is_some() {
315 data
.keep_hourly
= update
.keep_hourly
;
317 if update
.keep_daily
.is_some() {
318 data
.keep_daily
= update
.keep_daily
;
320 if update
.keep_weekly
.is_some() {
321 data
.keep_weekly
= update
.keep_weekly
;
323 if update
.keep_monthly
.is_some() {
324 data
.keep_monthly
= update
.keep_monthly
;
326 if update
.keep_yearly
.is_some() {
327 data
.keep_yearly
= update
.keep_yearly
;
330 if let Some(notify_str
) = update
.notify
{
331 let value
= DatastoreNotify
::API_SCHEMA
.parse_property_string(¬ify_str
)?
;
332 let notify
: DatastoreNotify
= serde_json
::from_value(value
)?
;
333 if let DatastoreNotify
{
341 data
.notify
= Some(notify_str
);
344 if update
.verify_new
.is_some() {
345 data
.verify_new
= update
.verify_new
;
348 if update
.notify_user
.is_some() {
349 data
.notify_user
= update
.notify_user
;
352 if update
.tuning
.is_some() {
353 data
.tuning
= update
.tuning
;
356 if update
.maintenance_mode
.is_some() {
357 data
.maintenance_mode
= update
.maintenance_mode
;
360 config
.set_data(&name
, "datastore", &data
)?
;
362 pbs_config
::datastore
::save_config(&config
)?
;
364 // we want to reset the statefiles, to avoid an immediate action in some cases
365 // (e.g. going from monthly to weekly in the second week of the month)
366 if gc_schedule_changed
{
367 jobstate
::update_job_last_run_time("garbage_collection", &name
)?
;
370 if prune_schedule_changed
{
371 jobstate
::update_job_last_run_time("prune", &name
)?
;
382 schema
: DATASTORE_SCHEMA
,
384 "keep-job-configs": {
385 description
: "If enabled, the job configurations related to this datastore will be kept.",
392 schema
: PROXMOX_CONFIG_DIGEST_SCHEMA
,
397 permission
: &Permission
::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_ALLOCATE
, false),
400 /// Remove a datastore configuration.
401 pub async
fn delete_datastore(
403 keep_job_configs
: bool
,
404 digest
: Option
<String
>,
405 rpcenv
: &mut dyn RpcEnvironment
,
406 ) -> Result
<(), Error
> {
407 let _lock
= pbs_config
::datastore
::lock_config()?
;
409 let (mut config
, expected_digest
) = pbs_config
::datastore
::config()?
;
411 if let Some(ref digest
) = digest
{
412 let digest
= <[u8; 32]>::from_hex(digest
)?
;
413 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
416 match config
.sections
.get(&name
) {
418 config
.sections
.remove(&name
);
420 None
=> http_bail
!(NOT_FOUND
, "datastore '{}' does not exist.", name
),
423 if !keep_job_configs
{
424 for job
in list_verification_jobs(Some(name
.clone()), Value
::Null
, rpcenv
)?
{
425 delete_verification_job(job
.config
.id
, None
, rpcenv
)?
427 for job
in list_sync_jobs(Some(name
.clone()), Value
::Null
, rpcenv
)?
{
428 delete_sync_job(job
.config
.id
, None
, rpcenv
)?
431 let tape_jobs
= list_tape_backup_jobs(Value
::Null
, rpcenv
)?
;
432 for job_config
in tape_jobs
434 .filter(|config
| config
.setup
.store
== name
)
436 delete_tape_backup_job(job_config
.id
, None
, rpcenv
)?
;
440 pbs_config
::datastore
::save_config(&config
)?
;
443 let _
= jobstate
::remove_state_file("prune", &name
);
444 let _
= jobstate
::remove_state_file("garbage_collection", &name
);
446 crate::server
::notify_datastore_removed().await?
;
451 const ITEM_ROUTER
: Router
= Router
::new()
452 .get(&API_METHOD_READ_DATASTORE
)
453 .put(&API_METHOD_UPDATE_DATASTORE
)
454 .delete(&API_METHOD_DELETE_DATASTORE
);
456 pub const ROUTER
: Router
= Router
::new()
457 .get(&API_METHOD_LIST_DATASTORES
)
458 .post(&API_METHOD_CREATE_DATASTORE
)
459 .match_all("name", &ITEM_ROUTER
);