]> git.proxmox.com Git - proxmox-backup.git/blame - src/api2/config/datastore.rs
backup/chunk_store: optionally log progress on creation
[proxmox-backup.git] / src / api2 / config / datastore.rs
CommitLineData
a2479cfa 1use std::path::PathBuf;
6ce50400 2
f7d4e4b5 3use anyhow::{bail, Error};
5e62d19c 4use serde_json::Value;
0a00f6e0 5use ::serde::{Deserialize, Serialize};
6ce50400 6
67f7ffd0 7use proxmox::api::{api, Router, RpcEnvironment, Permission};
4708f4fc 8use proxmox::api::section_config::SectionConfigData;
c26c9390 9use proxmox::api::schema::parse_property_string;
a2479cfa 10
66c49c21 11use crate::api2::types::*;
a2479cfa 12use crate::backup::*;
b93bbab4 13use crate::config::cached_user_info::CachedUserInfo;
67f7ffd0 14use crate::config::datastore::{self, DataStoreConfig, DIR_NAME_SCHEMA};
41bfd249 15use crate::config::acl::{PRIV_DATASTORE_ALLOCATE, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_MODIFY};
4708f4fc 16use crate::server::{jobstate, WorkerTask};
567713b4 17
688fbe07
DM
18#[api(
19 input: {
20 properties: {},
21 },
22 returns: {
f3ec5dae 23 description: "List the configured datastores (with config digest).",
688fbe07 24 type: Array,
67f7ffd0 25 items: { type: datastore::DataStoreConfig },
688fbe07 26 },
c0ef209a 27 access: {
b93bbab4 28 permission: &Permission::Anybody,
c0ef209a 29 },
688fbe07
DM
30)]
31/// List all datastores
32pub fn list_datastores(
6049b71f 33 _param: Value,
67f7ffd0
DM
34 mut rpcenv: &mut dyn RpcEnvironment,
35) -> Result<Vec<DataStoreConfig>, Error> {
567713b4 36
d0187a51 37 let (config, digest) = datastore::config()?;
b65eaac6 38
e6dc35ac 39 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
b93bbab4 40 let user_info = CachedUserInfo::new()?;
67f7ffd0
DM
41
42 rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
43
b93bbab4
FG
44 let list:Vec<DataStoreConfig> = config.convert_to_typed_array("datastore")?;
45 let filter_by_privs = |store: &DataStoreConfig| {
e6dc35ac 46 let user_privs = user_info.lookup_privs(&auth_id, &["datastore", &store.name]);
b93bbab4
FG
47 (user_privs & PRIV_DATASTORE_AUDIT) != 0
48 };
49
50 Ok(list.into_iter().filter(filter_by_privs).collect())
ea0b8b6e
DM
51}
52
4708f4fc
DC
53pub(crate) fn do_create_datastore(
54 _lock: std::fs::File,
55 mut config: SectionConfigData,
56 datastore: DataStoreConfig,
2de4dc3a 57 worker: Option<&dyn crate::task::TaskState>,
4708f4fc
DC
58) -> Result<(), Error> {
59 let path: PathBuf = datastore.path.clone().into();
60
61 let backup_user = crate::backup::backup_user()?;
2de4dc3a 62 let _store = ChunkStore::create(&datastore.name, path, backup_user.uid, backup_user.gid, worker)?;
4708f4fc
DC
63
64 config.set_data(&datastore.name, "datastore", &datastore)?;
65
66 datastore::save_config(&config)?;
67
68 jobstate::create_state_file("prune", &datastore.name)?;
69 jobstate::create_state_file("garbage_collection", &datastore.name)?;
70
71 Ok(())
72}
67f7ffd0
DM
73
74// fixme: impl. const fn get_object_schema(datastore::DataStoreConfig::API_SCHEMA),
75// but this need support for match inside const fn
76// see: https://github.com/rust-lang/rust/issues/49146
77
688fbe07
DM
78#[api(
79 protected: true,
80 input: {
81 properties: {
82 name: {
83 schema: DATASTORE_SCHEMA,
84 },
67f7ffd0
DM
85 path: {
86 schema: DIR_NAME_SCHEMA,
87 },
688fbe07
DM
88 comment: {
89 optional: true,
454c13ed 90 schema: SINGLE_LINE_COMMENT_SCHEMA,
688fbe07 91 },
6e545d00
DM
92 "notify-user": {
93 optional: true,
94 type: Userid,
95 },
96 "notify": {
97 optional: true,
c26c9390 98 schema: DATASTORE_NOTIFY_STRING_SCHEMA,
6e545d00 99 },
42fdbe51
DM
100 "gc-schedule": {
101 optional: true,
102 schema: GC_SCHEDULE_SCHEMA,
103 },
67f7ffd0
DM
104 "prune-schedule": {
105 optional: true,
106 schema: PRUNE_SCHEDULE_SCHEMA,
107 },
108 "keep-last": {
109 optional: true,
110 schema: PRUNE_SCHEMA_KEEP_LAST,
111 },
112 "keep-hourly": {
113 optional: true,
114 schema: PRUNE_SCHEMA_KEEP_HOURLY,
115 },
116 "keep-daily": {
117 optional: true,
118 schema: PRUNE_SCHEMA_KEEP_DAILY,
119 },
120 "keep-weekly": {
121 optional: true,
122 schema: PRUNE_SCHEMA_KEEP_WEEKLY,
123 },
124 "keep-monthly": {
125 optional: true,
126 schema: PRUNE_SCHEMA_KEEP_MONTHLY,
127 },
128 "keep-yearly": {
129 optional: true,
130 schema: PRUNE_SCHEMA_KEEP_YEARLY,
688fbe07
DM
131 },
132 },
133 },
c0ef209a 134 access: {
41bfd249 135 permission: &Permission::Privilege(&["datastore"], PRIV_DATASTORE_ALLOCATE, false),
c0ef209a 136 },
688fbe07
DM
137)]
138/// Create new datastore config.
4708f4fc
DC
139pub fn create_datastore(
140 param: Value,
141 rpcenv: &mut dyn RpcEnvironment,
142) -> Result<String, Error> {
ea0b8b6e 143
b90036da 144 let lock = datastore::lock_config()?;
652c1190 145
44288184 146 let datastore: datastore::DataStoreConfig = serde_json::from_value(param)?;
652c1190 147
4708f4fc 148 let (config, _digest) = datastore::config()?;
652c1190 149
3984a5fd 150 if config.sections.get(&datastore.name).is_some() {
67f7ffd0 151 bail!("datastore '{}' already exists.", datastore.name);
652c1190
DM
152 }
153
4708f4fc 154 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
9866de5e 155
4708f4fc
DC
156 WorkerTask::new_thread(
157 "create-datastore",
158 Some(datastore.name.to_string()),
159 auth_id,
160 false,
2de4dc3a 161 move |worker| do_create_datastore(lock, config, datastore, Some(&worker)),
4708f4fc 162 )
6ce50400
DM
163}
164
c5799e40
DM
165#[api(
166 input: {
167 properties: {
168 name: {
169 schema: DATASTORE_SCHEMA,
170 },
171 },
172 },
9b93c620 173 returns: { type: datastore::DataStoreConfig },
c0ef209a
DM
174 access: {
175 permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_AUDIT, false),
176 },
c5799e40
DM
177)]
178/// Read a datastore configuration.
67f7ffd0
DM
179pub fn read_datastore(
180 name: String,
181 mut rpcenv: &mut dyn RpcEnvironment,
182) -> Result<DataStoreConfig, Error> {
c5799e40 183 let (config, digest) = datastore::config()?;
67f7ffd0
DM
184
185 let store_config = config.lookup("datastore", &name)?;
186 rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
187
188 Ok(store_config)
c5799e40
DM
189}
190
0a00f6e0
DM
191#[api()]
192#[derive(Serialize, Deserialize)]
42fdbe51 193#[serde(rename_all="kebab-case")]
0a00f6e0
DM
194#[allow(non_camel_case_types)]
195/// Deletable property name
196pub enum DeletableProperty {
197 /// Delete the comment property.
198 comment,
42fdbe51
DM
199 /// Delete the garbage collection schedule.
200 gc_schedule,
67f7ffd0
DM
201 /// Delete the prune job schedule.
202 prune_schedule,
203 /// Delete the keep-last property
204 keep_last,
205 /// Delete the keep-hourly property
206 keep_hourly,
207 /// Delete the keep-daily property
208 keep_daily,
209 /// Delete the keep-weekly property
210 keep_weekly,
211 /// Delete the keep-monthly property
212 keep_monthly,
213 /// Delete the keep-yearly property
214 keep_yearly,
ad53c1d6
TL
215 /// Delete the verify-new property
216 verify_new,
6e545d00
DM
217 /// Delete the notify-user property
218 notify_user,
219 /// Delete the notify property
220 notify,
0a00f6e0
DM
221}
222
c5799e40
DM
223#[api(
224 protected: true,
225 input: {
226 properties: {
227 name: {
228 schema: DATASTORE_SCHEMA,
229 },
230 comment: {
231 optional: true,
232 schema: SINGLE_LINE_COMMENT_SCHEMA,
233 },
6e545d00
DM
234 "notify-user": {
235 optional: true,
236 type: Userid,
237 },
238 "notify": {
239 optional: true,
c26c9390 240 schema: DATASTORE_NOTIFY_STRING_SCHEMA,
6e545d00 241 },
42fdbe51
DM
242 "gc-schedule": {
243 optional: true,
244 schema: GC_SCHEDULE_SCHEMA,
245 },
67f7ffd0
DM
246 "prune-schedule": {
247 optional: true,
248 schema: PRUNE_SCHEDULE_SCHEMA,
249 },
250 "keep-last": {
251 optional: true,
252 schema: PRUNE_SCHEMA_KEEP_LAST,
253 },
254 "keep-hourly": {
255 optional: true,
256 schema: PRUNE_SCHEMA_KEEP_HOURLY,
257 },
258 "keep-daily": {
259 optional: true,
260 schema: PRUNE_SCHEMA_KEEP_DAILY,
261 },
262 "keep-weekly": {
263 optional: true,
264 schema: PRUNE_SCHEMA_KEEP_WEEKLY,
265 },
266 "keep-monthly": {
267 optional: true,
268 schema: PRUNE_SCHEMA_KEEP_MONTHLY,
269 },
270 "keep-yearly": {
271 optional: true,
272 schema: PRUNE_SCHEMA_KEEP_YEARLY,
273 },
ad53c1d6
TL
274 "verify-new": {
275 description: "If enabled, all new backups will be verified right after completion.",
276 type: bool,
277 optional: true,
278 default: false,
279 },
0a00f6e0
DM
280 delete: {
281 description: "List of properties to delete.",
282 type: Array,
283 optional: true,
284 items: {
285 type: DeletableProperty,
286 }
287 },
002a191a
DM
288 digest: {
289 optional: true,
290 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
291 },
c5799e40
DM
292 },
293 },
c0ef209a 294 access: {
9c7fe29d 295 permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_MODIFY, false),
c0ef209a 296 },
c5799e40 297)]
2ea7bf1b 298/// Update datastore config.
367c0ff7 299#[allow(clippy::too_many_arguments)]
c5799e40
DM
300pub fn update_datastore(
301 name: String,
302 comment: Option<String>,
42fdbe51 303 gc_schedule: Option<String>,
67f7ffd0 304 prune_schedule: Option<String>,
872062ee
DM
305 keep_last: Option<u64>,
306 keep_hourly: Option<u64>,
307 keep_daily: Option<u64>,
308 keep_weekly: Option<u64>,
309 keep_monthly: Option<u64>,
310 keep_yearly: Option<u64>,
ad53c1d6 311 verify_new: Option<bool>,
c26c9390 312 notify: Option<String>,
6e545d00 313 notify_user: Option<Userid>,
0a00f6e0 314 delete: Option<Vec<DeletableProperty>>,
002a191a 315 digest: Option<String>,
c5799e40
DM
316) -> Result<(), Error> {
317
b90036da 318 let _lock = datastore::lock_config()?;
347834df 319
c5799e40 320 // pass/compare digest
002a191a
DM
321 let (mut config, expected_digest) = datastore::config()?;
322
323 if let Some(ref digest) = digest {
324 let digest = proxmox::tools::hex_to_digest(digest)?;
325 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
326 }
c5799e40
DM
327
328 let mut data: datastore::DataStoreConfig = config.lookup("datastore", &name)?;
329
0a00f6e0
DM
330 if let Some(delete) = delete {
331 for delete_prop in delete {
332 match delete_prop {
333 DeletableProperty::comment => { data.comment = None; },
42fdbe51 334 DeletableProperty::gc_schedule => { data.gc_schedule = None; },
67f7ffd0
DM
335 DeletableProperty::prune_schedule => { data.prune_schedule = None; },
336 DeletableProperty::keep_last => { data.keep_last = None; },
337 DeletableProperty::keep_hourly => { data.keep_hourly = None; },
338 DeletableProperty::keep_daily => { data.keep_daily = None; },
339 DeletableProperty::keep_weekly => { data.keep_weekly = None; },
340 DeletableProperty::keep_monthly => { data.keep_monthly = None; },
341 DeletableProperty::keep_yearly => { data.keep_yearly = None; },
ad53c1d6 342 DeletableProperty::verify_new => { data.verify_new = None; },
6e545d00
DM
343 DeletableProperty::notify => { data.notify = None; },
344 DeletableProperty::notify_user => { data.notify_user = None; },
0a00f6e0
DM
345 }
346 }
347 }
348
c5799e40
DM
349 if let Some(comment) = comment {
350 let comment = comment.trim().to_string();
351 if comment.is_empty() {
352 data.comment = None;
353 } else {
354 data.comment = Some(comment);
355 }
356 }
c5799e40 357
d7a122a0
DC
358 let mut gc_schedule_changed = false;
359 if gc_schedule.is_some() {
360 gc_schedule_changed = data.gc_schedule != gc_schedule;
361 data.gc_schedule = gc_schedule;
362 }
363
9866de5e
DC
364 let mut prune_schedule_changed = false;
365 if prune_schedule.is_some() {
d7a122a0 366 prune_schedule_changed = data.prune_schedule != prune_schedule;
9866de5e
DC
367 data.prune_schedule = prune_schedule;
368 }
d7a122a0 369
67f7ffd0
DM
370 if keep_last.is_some() { data.keep_last = keep_last; }
371 if keep_hourly.is_some() { data.keep_hourly = keep_hourly; }
372 if keep_daily.is_some() { data.keep_daily = keep_daily; }
373 if keep_weekly.is_some() { data.keep_weekly = keep_weekly; }
374 if keep_monthly.is_some() { data.keep_monthly = keep_monthly; }
375 if keep_yearly.is_some() { data.keep_yearly = keep_yearly; }
42fdbe51 376
c26c9390
DM
377 if let Some(notify_str) = notify {
378 let value = parse_property_string(&notify_str, &DatastoreNotify::API_SCHEMA)?;
379 let notify: DatastoreNotify = serde_json::from_value(value)?;
380 if let DatastoreNotify { gc: None, verify: None, sync: None } = notify {
381 data.notify = None;
382 } else {
383 data.notify = Some(notify_str);
384 }
385 }
ad53c1d6
TL
386 if verify_new.is_some() { data.verify_new = verify_new; }
387
6e545d00
DM
388 if notify_user.is_some() { data.notify_user = notify_user; }
389
c5799e40
DM
390 config.set_data(&name, "datastore", &data)?;
391
392 datastore::save_config(&config)?;
393
d7a122a0 394 // we want to reset the statefiles, to avoid an immediate action in some cases
9866de5e 395 // (e.g. going from monthly to weekly in the second week of the month)
d7a122a0 396 if gc_schedule_changed {
a588b679 397 jobstate::update_job_last_run_time("garbage_collection", &name)?;
d7a122a0
DC
398 }
399
9866de5e 400 if prune_schedule_changed {
a588b679 401 jobstate::update_job_last_run_time("prune", &name)?;
9866de5e
DC
402 }
403
c5799e40
DM
404 Ok(())
405}
406
688fbe07
DM
407#[api(
408 protected: true,
409 input: {
410 properties: {
411 name: {
412 schema: DATASTORE_SCHEMA,
413 },
c0ef209a
DM
414 digest: {
415 optional: true,
416 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
417 },
688fbe07
DM
418 },
419 },
c0ef209a 420 access: {
92dd02aa 421 permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_ALLOCATE, false),
c0ef209a 422 },
688fbe07
DM
423)]
424/// Remove a datastore configuration.
062cf75c 425pub async fn delete_datastore(name: String, digest: Option<String>) -> Result<(), Error> {
34d3ba52 426
b90036da 427 let _lock = datastore::lock_config()?;
34d3ba52 428
c0ef209a
DM
429 let (mut config, expected_digest) = datastore::config()?;
430
431 if let Some(ref digest) = digest {
432 let digest = proxmox::tools::hex_to_digest(digest)?;
433 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
434 }
34d3ba52 435
688fbe07
DM
436 match config.sections.get(&name) {
437 Some(_) => { config.sections.remove(&name); },
34d3ba52
DM
438 None => bail!("datastore '{}' does not exist.", name),
439 }
440
441 datastore::save_config(&config)?;
442
d7a122a0 443 // ignore errors
1298618a
DM
444 let _ = jobstate::remove_state_file("prune", &name);
445 let _ = jobstate::remove_state_file("garbage_collection", &name);
9866de5e 446
062cf75c
DC
447 crate::server::notify_datastore_removed().await?;
448
688fbe07 449 Ok(())
34d3ba52
DM
450}
451
c5799e40
DM
452const ITEM_ROUTER: Router = Router::new()
453 .get(&API_METHOD_READ_DATASTORE)
454 .put(&API_METHOD_UPDATE_DATASTORE)
455 .delete(&API_METHOD_DELETE_DATASTORE);
456
255f378a 457pub const ROUTER: Router = Router::new()
688fbe07
DM
458 .get(&API_METHOD_LIST_DATASTORES)
459 .post(&API_METHOD_CREATE_DATASTORE)
c5799e40 460 .match_all("name", &ITEM_ROUTER);