]>
Commit | Line | Data |
---|---|---|
a2479cfa | 1 | use std::path::PathBuf; |
6ce50400 | 2 | |
f7d4e4b5 | 3 | use anyhow::{bail, Error}; |
5e62d19c | 4 | use serde_json::Value; |
0a00f6e0 | 5 | use ::serde::{Deserialize, Serialize}; |
6ce50400 | 6 | |
bfa942c0 | 7 | use proxmox::api::{api, Router, RpcEnvironment, RpcEnvironmentType, Permission}; |
4708f4fc | 8 | use proxmox::api::section_config::SectionConfigData; |
a37c8d24 | 9 | use proxmox::api::schema::{ApiType, parse_property_string}; |
b2065dc7 WB |
10 | |
11 | use pbs_datastore::chunk_store::ChunkStore; | |
c23192d3 | 12 | use pbs_datastore::task::TaskState; |
21211748 | 13 | use pbs_config::BackupLockGuard; |
8cc3760e DM |
14 | use 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 |
21 | use crate::api2::config::sync::delete_sync_job; |
22 | use crate::api2::config::verify::delete_verification_job; | |
49f44ced | 23 | use crate::api2::config::tape_backup_job::{list_tape_backup_jobs, delete_tape_backup_job}; |
eb1c59cc HL |
24 | use crate::api2::admin::{ |
25 | sync::list_sync_jobs, | |
26 | verify::list_verification_jobs, | |
27 | }; | |
ba3d7e19 | 28 | use pbs_config::CachedUserInfo; |
e7d4be9d | 29 | |
4708f4fc | 30 | use 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 | |
46 | pub 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 | 67 | pub(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 | 103 | pub 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 |
142 | pub 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 | |
159 | pub 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 | 216 | pub 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(¬ify_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 |
336 | pub 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 |
382 | const 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 | 387 | pub 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); |