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