]> git.proxmox.com Git - proxmox-backup.git/blame - src/api2/config/datastore.rs
api/config: use param_bail for parameter errors
[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};
25877d05 6use hex::FromHex;
6ce50400 7
6ef1b649 8use proxmox_router::{Router, RpcEnvironment, RpcEnvironmentType, Permission};
8d6425aa 9use proxmox_schema::{api, param_bail, ApiType};
6ef1b649 10use proxmox_section_config::SectionConfigData;
25877d05 11use proxmox_sys::WorkerTaskContext;
b2065dc7
WB
12
13use pbs_datastore::chunk_store::ChunkStore;
21211748 14use pbs_config::BackupLockGuard;
8cc3760e
DM
15use pbs_api_types::{
16 Authid, DatastoreNotify,
17 DATASTORE_SCHEMA, PROXMOX_CONFIG_DIGEST_SCHEMA,
18 PRIV_DATASTORE_ALLOCATE, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_MODIFY,
e7d4be9d 19 DataStoreConfig, DataStoreConfigUpdater,
8cc3760e 20};
c23192d3 21
eb1c59cc
HL
22use crate::api2::config::sync::delete_sync_job;
23use crate::api2::config::verify::delete_verification_job;
49f44ced 24use crate::api2::config::tape_backup_job::{list_tape_backup_jobs, delete_tape_backup_job};
eb1c59cc
HL
25use crate::api2::admin::{
26 sync::list_sync_jobs,
27 verify::list_verification_jobs,
28};
ba3d7e19 29use pbs_config::CachedUserInfo;
c8449217 30
b9700a9f 31use proxmox_rest_server::WorkerTask;
e7d4be9d 32
b9700a9f 33use crate::server::jobstate;
567713b4 34
688fbe07
DM
35#[api(
36 input: {
37 properties: {},
38 },
39 returns: {
f3ec5dae 40 description: "List the configured datastores (with config digest).",
688fbe07 41 type: Array,
68e77657 42 items: { type: DataStoreConfig },
688fbe07 43 },
c0ef209a 44 access: {
b93bbab4 45 permission: &Permission::Anybody,
c0ef209a 46 },
688fbe07
DM
47)]
48/// List all datastores
49pub fn list_datastores(
6049b71f 50 _param: Value,
67f7ffd0
DM
51 mut rpcenv: &mut dyn RpcEnvironment,
52) -> Result<Vec<DataStoreConfig>, Error> {
567713b4 53
e7d4be9d 54 let (config, digest) = pbs_config::datastore::config()?;
b65eaac6 55
e6dc35ac 56 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
b93bbab4 57 let user_info = CachedUserInfo::new()?;
67f7ffd0 58
25877d05 59 rpcenv["digest"] = hex::encode(&digest).into();
67f7ffd0 60
b93bbab4
FG
61 let list:Vec<DataStoreConfig> = config.convert_to_typed_array("datastore")?;
62 let filter_by_privs = |store: &DataStoreConfig| {
e6dc35ac 63 let user_privs = user_info.lookup_privs(&auth_id, &["datastore", &store.name]);
b93bbab4
FG
64 (user_privs & PRIV_DATASTORE_AUDIT) != 0
65 };
66
67 Ok(list.into_iter().filter(filter_by_privs).collect())
ea0b8b6e
DM
68}
69
4708f4fc 70pub(crate) fn do_create_datastore(
7526d864 71 _lock: BackupLockGuard,
4708f4fc
DC
72 mut config: SectionConfigData,
73 datastore: DataStoreConfig,
c8449217 74 worker: Option<&dyn WorkerTaskContext>,
4708f4fc
DC
75) -> Result<(), Error> {
76 let path: PathBuf = datastore.path.clone().into();
77
21211748 78 let backup_user = pbs_config::backup_user()?;
2de4dc3a 79 let _store = ChunkStore::create(&datastore.name, path, backup_user.uid, backup_user.gid, worker)?;
4708f4fc
DC
80
81 config.set_data(&datastore.name, "datastore", &datastore)?;
82
e7d4be9d 83 pbs_config::datastore::save_config(&config)?;
4708f4fc
DC
84
85 jobstate::create_state_file("prune", &datastore.name)?;
86 jobstate::create_state_file("garbage_collection", &datastore.name)?;
87
88 Ok(())
89}
67f7ffd0 90
688fbe07
DM
91#[api(
92 protected: true,
93 input: {
94 properties: {
68e77657
DM
95 config: {
96 type: DataStoreConfig,
97 flatten: true,
688fbe07
DM
98 },
99 },
100 },
c0ef209a 101 access: {
41bfd249 102 permission: &Permission::Privilege(&["datastore"], PRIV_DATASTORE_ALLOCATE, false),
c0ef209a 103 },
688fbe07
DM
104)]
105/// Create new datastore config.
4708f4fc 106pub fn create_datastore(
68e77657 107 config: DataStoreConfig,
4708f4fc
DC
108 rpcenv: &mut dyn RpcEnvironment,
109) -> Result<String, Error> {
ea0b8b6e 110
e7d4be9d 111 let lock = pbs_config::datastore::lock_config()?;
652c1190 112
e7d4be9d 113 let (section_config, _digest) = pbs_config::datastore::config()?;
652c1190 114
68e77657 115 if section_config.sections.get(&config.name).is_some() {
8d6425aa 116 param_bail!("name", "datastore '{}' already exists.", config.name);
652c1190
DM
117 }
118
4708f4fc 119 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
bfa942c0 120 let to_stdout = rpcenv.env_type() == RpcEnvironmentType::CLI;
9866de5e 121
4708f4fc
DC
122 WorkerTask::new_thread(
123 "create-datastore",
68e77657 124 Some(config.name.to_string()),
049a22a3 125 auth_id.to_string(),
bfa942c0 126 to_stdout,
049a22a3 127 move |worker| do_create_datastore(lock, section_config, config, Some(&worker)),
4708f4fc 128 )
6ce50400
DM
129}
130
c5799e40
DM
131#[api(
132 input: {
133 properties: {
134 name: {
135 schema: DATASTORE_SCHEMA,
136 },
137 },
138 },
68e77657 139 returns: { type: DataStoreConfig },
c0ef209a
DM
140 access: {
141 permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_AUDIT, false),
142 },
c5799e40
DM
143)]
144/// Read a datastore configuration.
67f7ffd0
DM
145pub fn read_datastore(
146 name: String,
147 mut rpcenv: &mut dyn RpcEnvironment,
148) -> Result<DataStoreConfig, Error> {
e7d4be9d 149 let (config, digest) = pbs_config::datastore::config()?;
67f7ffd0
DM
150
151 let store_config = config.lookup("datastore", &name)?;
25877d05 152 rpcenv["digest"] = hex::encode(&digest).into();
67f7ffd0
DM
153
154 Ok(store_config)
c5799e40
DM
155}
156
0a00f6e0
DM
157#[api()]
158#[derive(Serialize, Deserialize)]
42fdbe51 159#[serde(rename_all="kebab-case")]
0a00f6e0
DM
160#[allow(non_camel_case_types)]
161/// Deletable property name
162pub enum DeletableProperty {
163 /// Delete the comment property.
164 comment,
42fdbe51
DM
165 /// Delete the garbage collection schedule.
166 gc_schedule,
67f7ffd0
DM
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,
ad53c1d6
TL
181 /// Delete the verify-new property
182 verify_new,
6e545d00
DM
183 /// Delete the notify-user property
184 notify_user,
185 /// Delete the notify property
186 notify,
fef61684
DC
187 /// Delete the tuning property
188 tuning,
0a00f6e0
DM
189}
190
c5799e40
DM
191#[api(
192 protected: true,
193 input: {
194 properties: {
195 name: {
196 schema: DATASTORE_SCHEMA,
197 },
a8a20e92
DM
198 update: {
199 type: DataStoreConfigUpdater,
200 flatten: true,
ad53c1d6 201 },
0a00f6e0
DM
202 delete: {
203 description: "List of properties to delete.",
204 type: Array,
205 optional: true,
206 items: {
207 type: DeletableProperty,
208 }
209 },
002a191a
DM
210 digest: {
211 optional: true,
212 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
213 },
c5799e40
DM
214 },
215 },
c0ef209a 216 access: {
9c7fe29d 217 permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_MODIFY, false),
c0ef209a 218 },
c5799e40 219)]
2ea7bf1b 220/// Update datastore config.
c5799e40 221pub fn update_datastore(
a8a20e92 222 update: DataStoreConfigUpdater,
c5799e40 223 name: String,
0a00f6e0 224 delete: Option<Vec<DeletableProperty>>,
002a191a 225 digest: Option<String>,
c5799e40
DM
226) -> Result<(), Error> {
227
e7d4be9d 228 let _lock = pbs_config::datastore::lock_config()?;
347834df 229
c5799e40 230 // pass/compare digest
e7d4be9d 231 let (mut config, expected_digest) = pbs_config::datastore::config()?;
002a191a
DM
232
233 if let Some(ref digest) = digest {
25877d05 234 let digest = <[u8; 32]>::from_hex(digest)?;
002a191a
DM
235 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
236 }
c5799e40 237
68e77657 238 let mut data: DataStoreConfig = config.lookup("datastore", &name)?;
c5799e40 239
0a00f6e0
DM
240 if let Some(delete) = delete {
241 for delete_prop in delete {
242 match delete_prop {
243 DeletableProperty::comment => { data.comment = None; },
42fdbe51 244 DeletableProperty::gc_schedule => { data.gc_schedule = None; },
67f7ffd0
DM
245 DeletableProperty::prune_schedule => { data.prune_schedule = None; },
246 DeletableProperty::keep_last => { data.keep_last = None; },
247 DeletableProperty::keep_hourly => { data.keep_hourly = None; },
248 DeletableProperty::keep_daily => { data.keep_daily = None; },
249 DeletableProperty::keep_weekly => { data.keep_weekly = None; },
250 DeletableProperty::keep_monthly => { data.keep_monthly = None; },
251 DeletableProperty::keep_yearly => { data.keep_yearly = None; },
ad53c1d6 252 DeletableProperty::verify_new => { data.verify_new = None; },
6e545d00
DM
253 DeletableProperty::notify => { data.notify = None; },
254 DeletableProperty::notify_user => { data.notify_user = None; },
fef61684 255 DeletableProperty::tuning => { data.tuning = None; },
0a00f6e0
DM
256 }
257 }
258 }
259
a8a20e92 260 if let Some(comment) = update.comment {
c5799e40
DM
261 let comment = comment.trim().to_string();
262 if comment.is_empty() {
263 data.comment = None;
264 } else {
265 data.comment = Some(comment);
266 }
267 }
c5799e40 268
d7a122a0 269 let mut gc_schedule_changed = false;
a8a20e92
DM
270 if update.gc_schedule.is_some() {
271 gc_schedule_changed = data.gc_schedule != update.gc_schedule;
272 data.gc_schedule = update.gc_schedule;
d7a122a0
DC
273 }
274
9866de5e 275 let mut prune_schedule_changed = false;
a8a20e92
DM
276 if update.prune_schedule.is_some() {
277 prune_schedule_changed = data.prune_schedule != update.prune_schedule;
278 data.prune_schedule = update.prune_schedule;
9866de5e 279 }
d7a122a0 280
a8a20e92
DM
281 if update.keep_last.is_some() { data.keep_last = update.keep_last; }
282 if update.keep_hourly.is_some() { data.keep_hourly = update.keep_hourly; }
283 if update.keep_daily.is_some() { data.keep_daily = update.keep_daily; }
284 if update.keep_weekly.is_some() { data.keep_weekly = update.keep_weekly; }
285 if update.keep_monthly.is_some() { data.keep_monthly = update.keep_monthly; }
286 if update.keep_yearly.is_some() { data.keep_yearly = update.keep_yearly; }
42fdbe51 287
a8a20e92 288 if let Some(notify_str) = update.notify {
9fa3026a 289 let value = DatastoreNotify::API_SCHEMA.parse_property_string(&notify_str)?;
c26c9390
DM
290 let notify: DatastoreNotify = serde_json::from_value(value)?;
291 if let DatastoreNotify { gc: None, verify: None, sync: None } = notify {
292 data.notify = None;
293 } else {
294 data.notify = Some(notify_str);
295 }
296 }
a8a20e92 297 if update.verify_new.is_some() { data.verify_new = update.verify_new; }
ad53c1d6 298
a8a20e92 299 if update.notify_user.is_some() { data.notify_user = update.notify_user; }
6e545d00 300
fef61684
DC
301 if update.tuning.is_some() { data.tuning = update.tuning; }
302
c5799e40
DM
303 config.set_data(&name, "datastore", &data)?;
304
e7d4be9d 305 pbs_config::datastore::save_config(&config)?;
c5799e40 306
d7a122a0 307 // we want to reset the statefiles, to avoid an immediate action in some cases
9866de5e 308 // (e.g. going from monthly to weekly in the second week of the month)
d7a122a0 309 if gc_schedule_changed {
a588b679 310 jobstate::update_job_last_run_time("garbage_collection", &name)?;
d7a122a0
DC
311 }
312
9866de5e 313 if prune_schedule_changed {
a588b679 314 jobstate::update_job_last_run_time("prune", &name)?;
9866de5e
DC
315 }
316
c5799e40
DM
317 Ok(())
318}
319
688fbe07
DM
320#[api(
321 protected: true,
322 input: {
323 properties: {
324 name: {
325 schema: DATASTORE_SCHEMA,
326 },
eb1c59cc
HL
327 "keep-job-configs": {
328 description: "If enabled, the job configurations related to this datastore will be kept.",
329 type: bool,
330 optional: true,
331 default: false,
332 },
c0ef209a
DM
333 digest: {
334 optional: true,
335 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
336 },
688fbe07
DM
337 },
338 },
c0ef209a 339 access: {
92dd02aa 340 permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_ALLOCATE, false),
c0ef209a 341 },
688fbe07
DM
342)]
343/// Remove a datastore configuration.
eb1c59cc
HL
344pub async fn delete_datastore(
345 name: String,
346 keep_job_configs: bool,
347 digest: Option<String>,
348 rpcenv: &mut dyn RpcEnvironment,
349) -> Result<(), Error> {
34d3ba52 350
e7d4be9d 351 let _lock = pbs_config::datastore::lock_config()?;
34d3ba52 352
e7d4be9d 353 let (mut config, expected_digest) = pbs_config::datastore::config()?;
c0ef209a
DM
354
355 if let Some(ref digest) = digest {
25877d05 356 let digest = <[u8; 32]>::from_hex(digest)?;
c0ef209a
DM
357 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
358 }
34d3ba52 359
688fbe07
DM
360 match config.sections.get(&name) {
361 Some(_) => { config.sections.remove(&name); },
34d3ba52
DM
362 None => bail!("datastore '{}' does not exist.", name),
363 }
364
eb1c59cc
HL
365 if !keep_job_configs {
366 for job in list_verification_jobs(Some(name.clone()), Value::Null, rpcenv)? {
367 delete_verification_job(job.config.id, None, rpcenv)?
368 }
369 for job in list_sync_jobs(Some(name.clone()), Value::Null, rpcenv)? {
370 delete_sync_job(job.config.id, None, rpcenv)?
371 }
49f44ced
DC
372
373 let tape_jobs = list_tape_backup_jobs(Value::Null, rpcenv)?;
374 for job_config in tape_jobs.into_iter().filter(|config| config.setup.store == name) {
375 delete_tape_backup_job(job_config.id, None, rpcenv)?;
376 }
eb1c59cc
HL
377 }
378
e7d4be9d 379 pbs_config::datastore::save_config(&config)?;
34d3ba52 380
d7a122a0 381 // ignore errors
1298618a
DM
382 let _ = jobstate::remove_state_file("prune", &name);
383 let _ = jobstate::remove_state_file("garbage_collection", &name);
9866de5e 384
062cf75c
DC
385 crate::server::notify_datastore_removed().await?;
386
688fbe07 387 Ok(())
34d3ba52
DM
388}
389
c5799e40
DM
390const ITEM_ROUTER: Router = Router::new()
391 .get(&API_METHOD_READ_DATASTORE)
392 .put(&API_METHOD_UPDATE_DATASTORE)
393 .delete(&API_METHOD_DELETE_DATASTORE);
394
255f378a 395pub const ROUTER: Router = Router::new()
688fbe07
DM
396 .get(&API_METHOD_LIST_DATASTORES)
397 .post(&API_METHOD_CREATE_DATASTORE)
c5799e40 398 .match_all("name", &ITEM_ROUTER);