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