]> git.proxmox.com Git - proxmox-backup.git/blame_incremental - src/api2/config/datastore.rs
backup/chunk_store: optionally log progress on creation
[proxmox-backup.git] / src / api2 / config / datastore.rs
... / ...
CommitLineData
1use std::path::PathBuf;
2
3use anyhow::{bail, Error};
4use serde_json::Value;
5use ::serde::{Deserialize, Serialize};
6
7use proxmox::api::{api, Router, RpcEnvironment, Permission};
8use proxmox::api::section_config::SectionConfigData;
9use proxmox::api::schema::parse_property_string;
10
11use crate::api2::types::*;
12use crate::backup::*;
13use crate::config::cached_user_info::CachedUserInfo;
14use crate::config::datastore::{self, DataStoreConfig, DIR_NAME_SCHEMA};
15use crate::config::acl::{PRIV_DATASTORE_ALLOCATE, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_MODIFY};
16use crate::server::{jobstate, WorkerTask};
17
18#[api(
19 input: {
20 properties: {},
21 },
22 returns: {
23 description: "List the configured datastores (with config digest).",
24 type: Array,
25 items: { type: datastore::DataStoreConfig },
26 },
27 access: {
28 permission: &Permission::Anybody,
29 },
30)]
31/// List all datastores
32pub fn list_datastores(
33 _param: Value,
34 mut rpcenv: &mut dyn RpcEnvironment,
35) -> Result<Vec<DataStoreConfig>, Error> {
36
37 let (config, digest) = datastore::config()?;
38
39 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
40 let user_info = CachedUserInfo::new()?;
41
42 rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
43
44 let list:Vec<DataStoreConfig> = config.convert_to_typed_array("datastore")?;
45 let filter_by_privs = |store: &DataStoreConfig| {
46 let user_privs = user_info.lookup_privs(&auth_id, &["datastore", &store.name]);
47 (user_privs & PRIV_DATASTORE_AUDIT) != 0
48 };
49
50 Ok(list.into_iter().filter(filter_by_privs).collect())
51}
52
53pub(crate) fn do_create_datastore(
54 _lock: std::fs::File,
55 mut config: SectionConfigData,
56 datastore: DataStoreConfig,
57 worker: Option<&dyn crate::task::TaskState>,
58) -> Result<(), Error> {
59 let path: PathBuf = datastore.path.clone().into();
60
61 let backup_user = crate::backup::backup_user()?;
62 let _store = ChunkStore::create(&datastore.name, path, backup_user.uid, backup_user.gid, worker)?;
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}
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
78#[api(
79 protected: true,
80 input: {
81 properties: {
82 name: {
83 schema: DATASTORE_SCHEMA,
84 },
85 path: {
86 schema: DIR_NAME_SCHEMA,
87 },
88 comment: {
89 optional: true,
90 schema: SINGLE_LINE_COMMENT_SCHEMA,
91 },
92 "notify-user": {
93 optional: true,
94 type: Userid,
95 },
96 "notify": {
97 optional: true,
98 schema: DATASTORE_NOTIFY_STRING_SCHEMA,
99 },
100 "gc-schedule": {
101 optional: true,
102 schema: GC_SCHEDULE_SCHEMA,
103 },
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,
131 },
132 },
133 },
134 access: {
135 permission: &Permission::Privilege(&["datastore"], PRIV_DATASTORE_ALLOCATE, false),
136 },
137)]
138/// Create new datastore config.
139pub fn create_datastore(
140 param: Value,
141 rpcenv: &mut dyn RpcEnvironment,
142) -> Result<String, Error> {
143
144 let lock = datastore::lock_config()?;
145
146 let datastore: datastore::DataStoreConfig = serde_json::from_value(param)?;
147
148 let (config, _digest) = datastore::config()?;
149
150 if config.sections.get(&datastore.name).is_some() {
151 bail!("datastore '{}' already exists.", datastore.name);
152 }
153
154 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
155
156 WorkerTask::new_thread(
157 "create-datastore",
158 Some(datastore.name.to_string()),
159 auth_id,
160 false,
161 move |worker| do_create_datastore(lock, config, datastore, Some(&worker)),
162 )
163}
164
165#[api(
166 input: {
167 properties: {
168 name: {
169 schema: DATASTORE_SCHEMA,
170 },
171 },
172 },
173 returns: { type: datastore::DataStoreConfig },
174 access: {
175 permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_AUDIT, false),
176 },
177)]
178/// Read a datastore configuration.
179pub fn read_datastore(
180 name: String,
181 mut rpcenv: &mut dyn RpcEnvironment,
182) -> Result<DataStoreConfig, Error> {
183 let (config, digest) = datastore::config()?;
184
185 let store_config = config.lookup("datastore", &name)?;
186 rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
187
188 Ok(store_config)
189}
190
191#[api()]
192#[derive(Serialize, Deserialize)]
193#[serde(rename_all="kebab-case")]
194#[allow(non_camel_case_types)]
195/// Deletable property name
196pub enum DeletableProperty {
197 /// Delete the comment property.
198 comment,
199 /// Delete the garbage collection schedule.
200 gc_schedule,
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,
215 /// Delete the verify-new property
216 verify_new,
217 /// Delete the notify-user property
218 notify_user,
219 /// Delete the notify property
220 notify,
221}
222
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 },
234 "notify-user": {
235 optional: true,
236 type: Userid,
237 },
238 "notify": {
239 optional: true,
240 schema: DATASTORE_NOTIFY_STRING_SCHEMA,
241 },
242 "gc-schedule": {
243 optional: true,
244 schema: GC_SCHEDULE_SCHEMA,
245 },
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 },
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 },
280 delete: {
281 description: "List of properties to delete.",
282 type: Array,
283 optional: true,
284 items: {
285 type: DeletableProperty,
286 }
287 },
288 digest: {
289 optional: true,
290 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
291 },
292 },
293 },
294 access: {
295 permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_MODIFY, false),
296 },
297)]
298/// Update datastore config.
299#[allow(clippy::too_many_arguments)]
300pub fn update_datastore(
301 name: String,
302 comment: Option<String>,
303 gc_schedule: Option<String>,
304 prune_schedule: Option<String>,
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>,
311 verify_new: Option<bool>,
312 notify: Option<String>,
313 notify_user: Option<Userid>,
314 delete: Option<Vec<DeletableProperty>>,
315 digest: Option<String>,
316) -> Result<(), Error> {
317
318 let _lock = datastore::lock_config()?;
319
320 // pass/compare digest
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 }
327
328 let mut data: datastore::DataStoreConfig = config.lookup("datastore", &name)?;
329
330 if let Some(delete) = delete {
331 for delete_prop in delete {
332 match delete_prop {
333 DeletableProperty::comment => { data.comment = None; },
334 DeletableProperty::gc_schedule => { data.gc_schedule = None; },
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; },
342 DeletableProperty::verify_new => { data.verify_new = None; },
343 DeletableProperty::notify => { data.notify = None; },
344 DeletableProperty::notify_user => { data.notify_user = None; },
345 }
346 }
347 }
348
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 }
357
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
364 let mut prune_schedule_changed = false;
365 if prune_schedule.is_some() {
366 prune_schedule_changed = data.prune_schedule != prune_schedule;
367 data.prune_schedule = prune_schedule;
368 }
369
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; }
376
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 }
386 if verify_new.is_some() { data.verify_new = verify_new; }
387
388 if notify_user.is_some() { data.notify_user = notify_user; }
389
390 config.set_data(&name, "datastore", &data)?;
391
392 datastore::save_config(&config)?;
393
394 // we want to reset the statefiles, to avoid an immediate action in some cases
395 // (e.g. going from monthly to weekly in the second week of the month)
396 if gc_schedule_changed {
397 jobstate::update_job_last_run_time("garbage_collection", &name)?;
398 }
399
400 if prune_schedule_changed {
401 jobstate::update_job_last_run_time("prune", &name)?;
402 }
403
404 Ok(())
405}
406
407#[api(
408 protected: true,
409 input: {
410 properties: {
411 name: {
412 schema: DATASTORE_SCHEMA,
413 },
414 digest: {
415 optional: true,
416 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
417 },
418 },
419 },
420 access: {
421 permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_ALLOCATE, false),
422 },
423)]
424/// Remove a datastore configuration.
425pub async fn delete_datastore(name: String, digest: Option<String>) -> Result<(), Error> {
426
427 let _lock = datastore::lock_config()?;
428
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 }
435
436 match config.sections.get(&name) {
437 Some(_) => { config.sections.remove(&name); },
438 None => bail!("datastore '{}' does not exist.", name),
439 }
440
441 datastore::save_config(&config)?;
442
443 // ignore errors
444 let _ = jobstate::remove_state_file("prune", &name);
445 let _ = jobstate::remove_state_file("garbage_collection", &name);
446
447 crate::server::notify_datastore_removed().await?;
448
449 Ok(())
450}
451
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
457pub const ROUTER: Router = Router::new()
458 .get(&API_METHOD_LIST_DATASTORES)
459 .post(&API_METHOD_CREATE_DATASTORE)
460 .match_all("name", &ITEM_ROUTER);