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