]> git.proxmox.com Git - proxmox-backup.git/blame - src/api2/config/datastore.rs
fix non-camel-case enums
[proxmox-backup.git] / src / api2 / config / datastore.rs
CommitLineData
a2479cfa 1use std::path::PathBuf;
6ce50400 2
0a00f6e0 3use ::serde::{Deserialize, Serialize};
dc7a5b34 4use anyhow::Error;
25877d05 5use hex::FromHex;
dc7a5b34 6use serde_json::Value;
6ce50400 7
dc7a5b34 8use proxmox_router::{http_bail, Permission, Router, RpcEnvironment, RpcEnvironmentType};
8d6425aa 9use proxmox_schema::{api, param_bail, ApiType};
6ef1b649 10use proxmox_section_config::SectionConfigData;
857f346c 11use proxmox_sys::{task_warn, WorkerTaskContext};
b2065dc7 12
8cc3760e 13use pbs_api_types::{
647186dd
DC
14 Authid, DataStoreConfig, DataStoreConfigUpdater, DatastoreNotify, DatastoreTuning,
15 DATASTORE_SCHEMA, PRIV_DATASTORE_ALLOCATE, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_MODIFY,
857f346c 16 PROXMOX_CONFIG_DIGEST_SCHEMA, UPID_SCHEMA,
8cc3760e 17};
dc7a5b34
TL
18use pbs_config::BackupLockGuard;
19use pbs_datastore::chunk_store::ChunkStore;
c23192d3 20
ca1da2cb
HL
21use crate::api2::admin::{
22 prune::list_prune_jobs, sync::list_sync_jobs, verify::list_verification_jobs,
23};
24use crate::api2::config::prune::delete_prune_job;
eb1c59cc 25use crate::api2::config::sync::delete_sync_job;
dc7a5b34 26use crate::api2::config::tape_backup_job::{delete_tape_backup_job, list_tape_backup_jobs};
eb1c59cc 27use crate::api2::config::verify::delete_verification_job;
ba3d7e19 28use pbs_config::CachedUserInfo;
c8449217 29
b9700a9f 30use proxmox_rest_server::WorkerTask;
e7d4be9d 31
b9700a9f 32use crate::server::jobstate;
567713b4 33
688fbe07
DM
34#[api(
35 input: {
36 properties: {},
37 },
38 returns: {
f3ec5dae 39 description: "List the configured datastores (with config digest).",
688fbe07 40 type: Array,
68e77657 41 items: { type: DataStoreConfig },
688fbe07 42 },
c0ef209a 43 access: {
b93bbab4 44 permission: &Permission::Anybody,
c0ef209a 45 },
688fbe07
DM
46)]
47/// List all datastores
48pub fn list_datastores(
6049b71f 49 _param: Value,
41c1a179 50 rpcenv: &mut dyn RpcEnvironment,
67f7ffd0 51) -> Result<Vec<DataStoreConfig>, Error> {
e7d4be9d 52 let (config, digest) = pbs_config::datastore::config()?;
b65eaac6 53
e6dc35ac 54 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
b93bbab4 55 let user_info = CachedUserInfo::new()?;
67f7ffd0 56
16f6766a 57 rpcenv["digest"] = hex::encode(digest).into();
67f7ffd0 58
dc7a5b34 59 let list: Vec<DataStoreConfig> = config.convert_to_typed_array("datastore")?;
b93bbab4 60 let filter_by_privs = |store: &DataStoreConfig| {
e6dc35ac 61 let user_privs = user_info.lookup_privs(&auth_id, &["datastore", &store.name]);
b93bbab4
FG
62 (user_privs & PRIV_DATASTORE_AUDIT) != 0
63 };
64
65 Ok(list.into_iter().filter(filter_by_privs).collect())
ea0b8b6e
DM
66}
67
4708f4fc 68pub(crate) fn do_create_datastore(
7526d864 69 _lock: BackupLockGuard,
4708f4fc
DC
70 mut config: SectionConfigData,
71 datastore: DataStoreConfig,
c8449217 72 worker: Option<&dyn WorkerTaskContext>,
4708f4fc
DC
73) -> Result<(), Error> {
74 let path: PathBuf = datastore.path.clone().into();
75
647186dd
DC
76 let tuning: DatastoreTuning = serde_json::from_value(
77 DatastoreTuning::API_SCHEMA
78 .parse_property_string(datastore.tuning.as_deref().unwrap_or(""))?,
79 )?;
21211748 80 let backup_user = pbs_config::backup_user()?;
dc7a5b34
TL
81 let _store = ChunkStore::create(
82 &datastore.name,
83 path,
84 backup_user.uid,
85 backup_user.gid,
86 worker,
647186dd 87 tuning.sync_level.unwrap_or_default(),
dc7a5b34 88 )?;
4708f4fc
DC
89
90 config.set_data(&datastore.name, "datastore", &datastore)?;
91
e7d4be9d 92 pbs_config::datastore::save_config(&config)?;
4708f4fc
DC
93
94 jobstate::create_state_file("prune", &datastore.name)?;
95 jobstate::create_state_file("garbage_collection", &datastore.name)?;
96
97 Ok(())
98}
67f7ffd0 99
688fbe07
DM
100#[api(
101 protected: true,
102 input: {
103 properties: {
68e77657
DM
104 config: {
105 type: DataStoreConfig,
106 flatten: true,
688fbe07
DM
107 },
108 },
109 },
c0ef209a 110 access: {
41bfd249 111 permission: &Permission::Privilege(&["datastore"], PRIV_DATASTORE_ALLOCATE, false),
c0ef209a 112 },
688fbe07
DM
113)]
114/// Create new datastore config.
4708f4fc 115pub fn create_datastore(
68e77657 116 config: DataStoreConfig,
4708f4fc
DC
117 rpcenv: &mut dyn RpcEnvironment,
118) -> Result<String, Error> {
e7d4be9d 119 let lock = pbs_config::datastore::lock_config()?;
652c1190 120
e7d4be9d 121 let (section_config, _digest) = pbs_config::datastore::config()?;
652c1190 122
68e77657 123 if section_config.sections.get(&config.name).is_some() {
8d6425aa 124 param_bail!("name", "datastore '{}' already exists.", config.name);
652c1190
DM
125 }
126
4708f4fc 127 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
bfa942c0 128 let to_stdout = rpcenv.env_type() == RpcEnvironmentType::CLI;
9866de5e 129
4708f4fc
DC
130 WorkerTask::new_thread(
131 "create-datastore",
68e77657 132 Some(config.name.to_string()),
049a22a3 133 auth_id.to_string(),
bfa942c0 134 to_stdout,
dc7a5b34 135 move |worker| do_create_datastore(lock, section_config, config, Some(&worker)),
4708f4fc 136 )
6ce50400
DM
137}
138
c5799e40
DM
139#[api(
140 input: {
141 properties: {
142 name: {
143 schema: DATASTORE_SCHEMA,
144 },
145 },
146 },
68e77657 147 returns: { type: DataStoreConfig },
c0ef209a
DM
148 access: {
149 permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_AUDIT, false),
150 },
c5799e40
DM
151)]
152/// Read a datastore configuration.
67f7ffd0
DM
153pub fn read_datastore(
154 name: String,
41c1a179 155 rpcenv: &mut dyn RpcEnvironment,
67f7ffd0 156) -> Result<DataStoreConfig, Error> {
e7d4be9d 157 let (config, digest) = pbs_config::datastore::config()?;
67f7ffd0
DM
158
159 let store_config = config.lookup("datastore", &name)?;
16f6766a 160 rpcenv["digest"] = hex::encode(digest).into();
67f7ffd0
DM
161
162 Ok(store_config)
c5799e40
DM
163}
164
0a00f6e0
DM
165#[api()]
166#[derive(Serialize, Deserialize)]
dc7a5b34 167#[serde(rename_all = "kebab-case")]
0a00f6e0
DM
168/// Deletable property name
169pub enum DeletableProperty {
170 /// Delete the comment property.
a2055c38 171 Comment,
42fdbe51 172 /// Delete the garbage collection schedule.
a2055c38 173 GcSchedule,
67f7ffd0 174 /// Delete the prune job schedule.
a2055c38 175 PruneSchedule,
67f7ffd0 176 /// Delete the keep-last property
a2055c38 177 KeepLast,
67f7ffd0 178 /// Delete the keep-hourly property
a2055c38 179 KeepHourly,
67f7ffd0 180 /// Delete the keep-daily property
a2055c38 181 KeepDaily,
67f7ffd0 182 /// Delete the keep-weekly property
a2055c38 183 KeepWeekly,
67f7ffd0 184 /// Delete the keep-monthly property
a2055c38 185 KeepMonthly,
67f7ffd0 186 /// Delete the keep-yearly property
a2055c38 187 KeepYearly,
ad53c1d6 188 /// Delete the verify-new property
a2055c38 189 VerifyNew,
6e545d00 190 /// Delete the notify-user property
a2055c38 191 NotifyUser,
6e545d00 192 /// Delete the notify property
a2055c38 193 Notify,
fef61684 194 /// Delete the tuning property
a2055c38 195 Tuning,
758c6ed5 196 /// Delete the maintenance-mode property
a2055c38 197 MaintenanceMode,
0a00f6e0
DM
198}
199
c5799e40
DM
200#[api(
201 protected: true,
202 input: {
203 properties: {
204 name: {
205 schema: DATASTORE_SCHEMA,
206 },
a8a20e92
DM
207 update: {
208 type: DataStoreConfigUpdater,
209 flatten: true,
ad53c1d6 210 },
0a00f6e0
DM
211 delete: {
212 description: "List of properties to delete.",
213 type: Array,
214 optional: true,
215 items: {
216 type: DeletableProperty,
217 }
218 },
002a191a
DM
219 digest: {
220 optional: true,
221 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
222 },
c5799e40
DM
223 },
224 },
c0ef209a 225 access: {
9c7fe29d 226 permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_MODIFY, false),
c0ef209a 227 },
c5799e40 228)]
2ea7bf1b 229/// Update datastore config.
c5799e40 230pub fn update_datastore(
a8a20e92 231 update: DataStoreConfigUpdater,
c5799e40 232 name: String,
0a00f6e0 233 delete: Option<Vec<DeletableProperty>>,
002a191a 234 digest: Option<String>,
c5799e40 235) -> Result<(), Error> {
e7d4be9d 236 let _lock = pbs_config::datastore::lock_config()?;
347834df 237
c5799e40 238 // pass/compare digest
e7d4be9d 239 let (mut config, expected_digest) = pbs_config::datastore::config()?;
002a191a
DM
240
241 if let Some(ref digest) = digest {
25877d05 242 let digest = <[u8; 32]>::from_hex(digest)?;
002a191a
DM
243 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
244 }
c5799e40 245
68e77657 246 let mut data: DataStoreConfig = config.lookup("datastore", &name)?;
c5799e40 247
dc7a5b34 248 if let Some(delete) = delete {
0a00f6e0
DM
249 for delete_prop in delete {
250 match delete_prop {
a2055c38 251 DeletableProperty::Comment => {
dc7a5b34
TL
252 data.comment = None;
253 }
a2055c38 254 DeletableProperty::GcSchedule => {
dc7a5b34
TL
255 data.gc_schedule = None;
256 }
a2055c38 257 DeletableProperty::PruneSchedule => {
dc7a5b34
TL
258 data.prune_schedule = None;
259 }
a2055c38 260 DeletableProperty::KeepLast => {
13477966 261 data.keep.keep_last = None;
dc7a5b34 262 }
a2055c38 263 DeletableProperty::KeepHourly => {
13477966 264 data.keep.keep_hourly = None;
dc7a5b34 265 }
a2055c38 266 DeletableProperty::KeepDaily => {
13477966 267 data.keep.keep_daily = None;
dc7a5b34 268 }
a2055c38 269 DeletableProperty::KeepWeekly => {
13477966 270 data.keep.keep_weekly = None;
dc7a5b34 271 }
a2055c38 272 DeletableProperty::KeepMonthly => {
13477966 273 data.keep.keep_monthly = None;
dc7a5b34 274 }
a2055c38 275 DeletableProperty::KeepYearly => {
13477966 276 data.keep.keep_yearly = None;
dc7a5b34 277 }
a2055c38 278 DeletableProperty::VerifyNew => {
dc7a5b34
TL
279 data.verify_new = None;
280 }
a2055c38 281 DeletableProperty::Notify => {
dc7a5b34
TL
282 data.notify = None;
283 }
a2055c38 284 DeletableProperty::NotifyUser => {
dc7a5b34
TL
285 data.notify_user = None;
286 }
a2055c38 287 DeletableProperty::Tuning => {
dc7a5b34
TL
288 data.tuning = None;
289 }
a2055c38 290 DeletableProperty::MaintenanceMode => {
dc7a5b34
TL
291 data.maintenance_mode = None;
292 }
0a00f6e0
DM
293 }
294 }
295 }
296
a8a20e92 297 if let Some(comment) = update.comment {
c5799e40
DM
298 let comment = comment.trim().to_string();
299 if comment.is_empty() {
300 data.comment = None;
301 } else {
302 data.comment = Some(comment);
303 }
304 }
c5799e40 305
d7a122a0 306 let mut gc_schedule_changed = false;
a8a20e92
DM
307 if update.gc_schedule.is_some() {
308 gc_schedule_changed = data.gc_schedule != update.gc_schedule;
309 data.gc_schedule = update.gc_schedule;
d7a122a0
DC
310 }
311
aa32a461
WB
312 macro_rules! prune_disabled {
313 ($(($param:literal, $($member:tt)+)),+) => {
314 $(
315 if update.$($member)+.is_some() {
316 param_bail!(
317 $param,
318 "datastore prune settings have been replaced by prune jobs",
319 );
320 }
321 )+
322 };
dc7a5b34 323 }
aa32a461
WB
324 prune_disabled! {
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)
dc7a5b34 332 }
42fdbe51 333
a8a20e92 334 if let Some(notify_str) = update.notify {
9fa3026a 335 let value = DatastoreNotify::API_SCHEMA.parse_property_string(&notify_str)?;
c26c9390 336 let notify: DatastoreNotify = serde_json::from_value(value)?;
dc7a5b34
TL
337 if let DatastoreNotify {
338 gc: None,
339 verify: None,
340 sync: None,
cf91a072 341 prune: None,
dc7a5b34
TL
342 } = notify
343 {
c26c9390
DM
344 data.notify = None;
345 } else {
346 data.notify = Some(notify_str);
347 }
348 }
dc7a5b34
TL
349 if update.verify_new.is_some() {
350 data.verify_new = update.verify_new;
351 }
ad53c1d6 352
dc7a5b34
TL
353 if update.notify_user.is_some() {
354 data.notify_user = update.notify_user;
355 }
6e545d00 356
dc7a5b34
TL
357 if update.tuning.is_some() {
358 data.tuning = update.tuning;
359 }
fef61684 360
dc7a5b34
TL
361 if update.maintenance_mode.is_some() {
362 data.maintenance_mode = update.maintenance_mode;
363 }
758c6ed5 364
c5799e40
DM
365 config.set_data(&name, "datastore", &data)?;
366
e7d4be9d 367 pbs_config::datastore::save_config(&config)?;
c5799e40 368
d7a122a0 369 // we want to reset the statefiles, to avoid an immediate action in some cases
9866de5e 370 // (e.g. going from monthly to weekly in the second week of the month)
d7a122a0 371 if gc_schedule_changed {
a588b679 372 jobstate::update_job_last_run_time("garbage_collection", &name)?;
d7a122a0
DC
373 }
374
c5799e40
DM
375 Ok(())
376}
377
688fbe07
DM
378#[api(
379 protected: true,
380 input: {
381 properties: {
382 name: {
383 schema: DATASTORE_SCHEMA,
384 },
eb1c59cc
HL
385 "keep-job-configs": {
386 description: "If enabled, the job configurations related to this datastore will be kept.",
387 type: bool,
388 optional: true,
389 default: false,
390 },
857f346c
WB
391 "destroy-data": {
392 description: "Delete the datastore's underlying contents",
393 optional: true,
394 type: bool,
395 default: false,
396 },
c0ef209a
DM
397 digest: {
398 optional: true,
399 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
400 },
688fbe07
DM
401 },
402 },
c0ef209a 403 access: {
92dd02aa 404 permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_ALLOCATE, false),
c0ef209a 405 },
857f346c
WB
406 returns: {
407 schema: UPID_SCHEMA,
408 },
688fbe07 409)]
857f346c 410/// Remove a datastore configuration and optionally delete all its contents.
eb1c59cc
HL
411pub async fn delete_datastore(
412 name: String,
413 keep_job_configs: bool,
857f346c 414 destroy_data: bool,
eb1c59cc
HL
415 digest: Option<String>,
416 rpcenv: &mut dyn RpcEnvironment,
857f346c 417) -> Result<String, Error> {
e7d4be9d 418 let _lock = pbs_config::datastore::lock_config()?;
34d3ba52 419
857f346c 420 let (config, expected_digest) = pbs_config::datastore::config()?;
c0ef209a
DM
421
422 if let Some(ref digest) = digest {
25877d05 423 let digest = <[u8; 32]>::from_hex(digest)?;
c0ef209a
DM
424 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
425 }
34d3ba52 426
857f346c
WB
427 if !config.sections.contains_key(&name) {
428 http_bail!(NOT_FOUND, "datastore '{}' does not exist.", name);
34d3ba52
DM
429 }
430
eb1c59cc
HL
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)?
434 }
435 for job in list_sync_jobs(Some(name.clone()), Value::Null, rpcenv)? {
436 delete_sync_job(job.config.id, None, rpcenv)?
437 }
ca1da2cb
HL
438 for job in list_prune_jobs(Some(name.clone()), Value::Null, rpcenv)? {
439 delete_prune_job(job.config.id, None, rpcenv)?
440 }
49f44ced 441
f6d6b5a3
HL
442 let (mut tree, _digest) = pbs_config::acl::config()?;
443 tree.delete_node(&format!("/datastore/{}", name));
444 pbs_config::acl::save_config(&tree)?;
445
49f44ced 446 let tape_jobs = list_tape_backup_jobs(Value::Null, rpcenv)?;
dc7a5b34
TL
447 for job_config in tape_jobs
448 .into_iter()
449 .filter(|config| config.setup.store == name)
450 {
49f44ced
DC
451 delete_tape_backup_job(job_config.id, None, rpcenv)?;
452 }
eb1c59cc
HL
453 }
454
857f346c
WB
455 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
456 let to_stdout = rpcenv.env_type() == RpcEnvironmentType::CLI;
34d3ba52 457
857f346c
WB
458 let upid = WorkerTask::new_thread(
459 "delete-datastore",
460 Some(name.clone()),
461 auth_id.to_string(),
462 to_stdout,
463 move |worker| {
464 pbs_datastore::DataStore::destroy(&name, destroy_data, &worker)?;
9866de5e 465
857f346c
WB
466 // ignore errors
467 let _ = jobstate::remove_state_file("prune", &name);
468 let _ = jobstate::remove_state_file("garbage_collection", &name);
062cf75c 469
857f346c
WB
470 if let Err(err) =
471 proxmox_async::runtime::block_on(crate::server::notify_datastore_removed())
472 {
473 task_warn!(worker, "failed to notify after datastore removal: {err}");
474 }
475
476 Ok(())
477 },
478 )?;
479
480 Ok(upid)
34d3ba52
DM
481}
482
c5799e40
DM
483const ITEM_ROUTER: Router = Router::new()
484 .get(&API_METHOD_READ_DATASTORE)
485 .put(&API_METHOD_UPDATE_DATASTORE)
486 .delete(&API_METHOD_DELETE_DATASTORE);
487
255f378a 488pub const ROUTER: Router = Router::new()
688fbe07
DM
489 .get(&API_METHOD_LIST_DATASTORES)
490 .post(&API_METHOD_CREATE_DATASTORE)
c5799e40 491 .match_all("name", &ITEM_ROUTER);