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