]> git.proxmox.com Git - proxmox-backup.git/blob - src/api2/config/datastore.rs
api: rustfmt
[proxmox-backup.git] / src / api2 / config / datastore.rs
1 use std::path::PathBuf;
2
3 use ::serde::{Deserialize, Serialize};
4 use anyhow::Error;
5 use hex::FromHex;
6 use serde_json::Value;
7
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;
12
13 use pbs_api_types::{
14 Authid, DataStoreConfig, DataStoreConfigUpdater, DatastoreNotify, DATASTORE_SCHEMA,
15 PRIV_DATASTORE_ALLOCATE, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_MODIFY,
16 PROXMOX_CONFIG_DIGEST_SCHEMA,
17 };
18 use pbs_config::BackupLockGuard;
19 use pbs_datastore::chunk_store::ChunkStore;
20
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;
26
27 use proxmox_rest_server::WorkerTask;
28
29 use crate::server::jobstate;
30
31 #[api(
32 input: {
33 properties: {},
34 },
35 returns: {
36 description: "List the configured datastores (with config digest).",
37 type: Array,
38 items: { type: DataStoreConfig },
39 },
40 access: {
41 permission: &Permission::Anybody,
42 },
43 )]
44 /// List all datastores
45 pub fn list_datastores(
46 _param: Value,
47 mut rpcenv: &mut dyn RpcEnvironment,
48 ) -> Result<Vec<DataStoreConfig>, Error> {
49 let (config, digest) = pbs_config::datastore::config()?;
50
51 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
52 let user_info = CachedUserInfo::new()?;
53
54 rpcenv["digest"] = hex::encode(&digest).into();
55
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
60 };
61
62 Ok(list.into_iter().filter(filter_by_privs).collect())
63 }
64
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();
72
73 let backup_user = pbs_config::backup_user()?;
74 let _store = ChunkStore::create(
75 &datastore.name,
76 path,
77 backup_user.uid,
78 backup_user.gid,
79 worker,
80 )?;
81
82 config.set_data(&datastore.name, "datastore", &datastore)?;
83
84 pbs_config::datastore::save_config(&config)?;
85
86 jobstate::create_state_file("prune", &datastore.name)?;
87 jobstate::create_state_file("garbage_collection", &datastore.name)?;
88
89 Ok(())
90 }
91
92 #[api(
93 protected: true,
94 input: {
95 properties: {
96 config: {
97 type: DataStoreConfig,
98 flatten: true,
99 },
100 },
101 },
102 access: {
103 permission: &Permission::Privilege(&["datastore"], PRIV_DATASTORE_ALLOCATE, false),
104 },
105 )]
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()?;
112
113 let (section_config, _digest) = pbs_config::datastore::config()?;
114
115 if section_config.sections.get(&config.name).is_some() {
116 param_bail!("name", "datastore '{}' already exists.", config.name);
117 }
118
119 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
120 let to_stdout = rpcenv.env_type() == RpcEnvironmentType::CLI;
121
122 WorkerTask::new_thread(
123 "create-datastore",
124 Some(config.name.to_string()),
125 auth_id.to_string(),
126 to_stdout,
127 move |worker| do_create_datastore(lock, section_config, config, Some(&worker)),
128 )
129 }
130
131 #[api(
132 input: {
133 properties: {
134 name: {
135 schema: DATASTORE_SCHEMA,
136 },
137 },
138 },
139 returns: { type: DataStoreConfig },
140 access: {
141 permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_AUDIT, false),
142 },
143 )]
144 /// Read a datastore configuration.
145 pub fn read_datastore(
146 name: String,
147 mut rpcenv: &mut dyn RpcEnvironment,
148 ) -> Result<DataStoreConfig, Error> {
149 let (config, digest) = pbs_config::datastore::config()?;
150
151 let store_config = config.lookup("datastore", &name)?;
152 rpcenv["digest"] = hex::encode(&digest).into();
153
154 Ok(store_config)
155 }
156
157 #[api()]
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.
164 comment,
165 /// Delete the garbage collection schedule.
166 gc_schedule,
167 /// Delete the prune job schedule.
168 prune_schedule,
169 /// Delete the keep-last property
170 keep_last,
171 /// Delete the keep-hourly property
172 keep_hourly,
173 /// Delete the keep-daily property
174 keep_daily,
175 /// Delete the keep-weekly property
176 keep_weekly,
177 /// Delete the keep-monthly property
178 keep_monthly,
179 /// Delete the keep-yearly property
180 keep_yearly,
181 /// Delete the verify-new property
182 verify_new,
183 /// Delete the notify-user property
184 notify_user,
185 /// Delete the notify property
186 notify,
187 /// Delete the tuning property
188 tuning,
189 /// Delete the maintenance-mode property
190 maintenance_mode,
191 }
192
193 #[api(
194 protected: true,
195 input: {
196 properties: {
197 name: {
198 schema: DATASTORE_SCHEMA,
199 },
200 update: {
201 type: DataStoreConfigUpdater,
202 flatten: true,
203 },
204 delete: {
205 description: "List of properties to delete.",
206 type: Array,
207 optional: true,
208 items: {
209 type: DeletableProperty,
210 }
211 },
212 digest: {
213 optional: true,
214 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
215 },
216 },
217 },
218 access: {
219 permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_MODIFY, false),
220 },
221 )]
222 /// Update datastore config.
223 pub fn update_datastore(
224 update: DataStoreConfigUpdater,
225 name: String,
226 delete: Option<Vec<DeletableProperty>>,
227 digest: Option<String>,
228 ) -> Result<(), Error> {
229 let _lock = pbs_config::datastore::lock_config()?;
230
231 // pass/compare digest
232 let (mut config, expected_digest) = pbs_config::datastore::config()?;
233
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)?;
237 }
238
239 let mut data: DataStoreConfig = config.lookup("datastore", &name)?;
240
241 if let Some(delete) = delete {
242 for delete_prop in delete {
243 match delete_prop {
244 DeletableProperty::comment => {
245 data.comment = None;
246 }
247 DeletableProperty::gc_schedule => {
248 data.gc_schedule = None;
249 }
250 DeletableProperty::prune_schedule => {
251 data.prune_schedule = None;
252 }
253 DeletableProperty::keep_last => {
254 data.keep_last = None;
255 }
256 DeletableProperty::keep_hourly => {
257 data.keep_hourly = None;
258 }
259 DeletableProperty::keep_daily => {
260 data.keep_daily = None;
261 }
262 DeletableProperty::keep_weekly => {
263 data.keep_weekly = None;
264 }
265 DeletableProperty::keep_monthly => {
266 data.keep_monthly = None;
267 }
268 DeletableProperty::keep_yearly => {
269 data.keep_yearly = None;
270 }
271 DeletableProperty::verify_new => {
272 data.verify_new = None;
273 }
274 DeletableProperty::notify => {
275 data.notify = None;
276 }
277 DeletableProperty::notify_user => {
278 data.notify_user = None;
279 }
280 DeletableProperty::tuning => {
281 data.tuning = None;
282 }
283 DeletableProperty::maintenance_mode => {
284 data.maintenance_mode = None;
285 }
286 }
287 }
288 }
289
290 if let Some(comment) = update.comment {
291 let comment = comment.trim().to_string();
292 if comment.is_empty() {
293 data.comment = None;
294 } else {
295 data.comment = Some(comment);
296 }
297 }
298
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;
303 }
304
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;
309 }
310
311 if update.keep_last.is_some() {
312 data.keep_last = update.keep_last;
313 }
314 if update.keep_hourly.is_some() {
315 data.keep_hourly = update.keep_hourly;
316 }
317 if update.keep_daily.is_some() {
318 data.keep_daily = update.keep_daily;
319 }
320 if update.keep_weekly.is_some() {
321 data.keep_weekly = update.keep_weekly;
322 }
323 if update.keep_monthly.is_some() {
324 data.keep_monthly = update.keep_monthly;
325 }
326 if update.keep_yearly.is_some() {
327 data.keep_yearly = update.keep_yearly;
328 }
329
330 if let Some(notify_str) = update.notify {
331 let value = DatastoreNotify::API_SCHEMA.parse_property_string(&notify_str)?;
332 let notify: DatastoreNotify = serde_json::from_value(value)?;
333 if let DatastoreNotify {
334 gc: None,
335 verify: None,
336 sync: None,
337 } = notify
338 {
339 data.notify = None;
340 } else {
341 data.notify = Some(notify_str);
342 }
343 }
344 if update.verify_new.is_some() {
345 data.verify_new = update.verify_new;
346 }
347
348 if update.notify_user.is_some() {
349 data.notify_user = update.notify_user;
350 }
351
352 if update.tuning.is_some() {
353 data.tuning = update.tuning;
354 }
355
356 if update.maintenance_mode.is_some() {
357 data.maintenance_mode = update.maintenance_mode;
358 }
359
360 config.set_data(&name, "datastore", &data)?;
361
362 pbs_config::datastore::save_config(&config)?;
363
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)?;
368 }
369
370 if prune_schedule_changed {
371 jobstate::update_job_last_run_time("prune", &name)?;
372 }
373
374 Ok(())
375 }
376
377 #[api(
378 protected: true,
379 input: {
380 properties: {
381 name: {
382 schema: DATASTORE_SCHEMA,
383 },
384 "keep-job-configs": {
385 description: "If enabled, the job configurations related to this datastore will be kept.",
386 type: bool,
387 optional: true,
388 default: false,
389 },
390 digest: {
391 optional: true,
392 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
393 },
394 },
395 },
396 access: {
397 permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_ALLOCATE, false),
398 },
399 )]
400 /// Remove a datastore configuration.
401 pub async fn delete_datastore(
402 name: String,
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()?;
408
409 let (mut config, expected_digest) = pbs_config::datastore::config()?;
410
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)?;
414 }
415
416 match config.sections.get(&name) {
417 Some(_) => {
418 config.sections.remove(&name);
419 }
420 None => http_bail!(NOT_FOUND, "datastore '{}' does not exist.", name),
421 }
422
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)?
426 }
427 for job in list_sync_jobs(Some(name.clone()), Value::Null, rpcenv)? {
428 delete_sync_job(job.config.id, None, rpcenv)?
429 }
430
431 let tape_jobs = list_tape_backup_jobs(Value::Null, rpcenv)?;
432 for job_config in tape_jobs
433 .into_iter()
434 .filter(|config| config.setup.store == name)
435 {
436 delete_tape_backup_job(job_config.id, None, rpcenv)?;
437 }
438 }
439
440 pbs_config::datastore::save_config(&config)?;
441
442 // ignore errors
443 let _ = jobstate::remove_state_file("prune", &name);
444 let _ = jobstate::remove_state_file("garbage_collection", &name);
445
446 crate::server::notify_datastore_removed().await?;
447
448 Ok(())
449 }
450
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);
455
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);