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