]> git.proxmox.com Git - proxmox-backup.git/blame - src/api2/config/changer.rs
api2/config/{drive, changer}: prevent adding same device multiple times
[proxmox-backup.git] / src / api2 / config / changer.rs
CommitLineData
50bf10ad 1use anyhow::{bail, Error};
38ae42b1 2use ::serde::{Deserialize, Serialize};
50bf10ad
DM
3use serde_json::Value;
4
38ae42b1
DM
5use proxmox::api::{
6 api,
7 Router,
8 RpcEnvironment,
9 schema::parse_property_string,
10};
50bf10ad
DM
11
12use crate::{
13 config,
14 api2::types::{
5ba83ed0 15 PROXMOX_CONFIG_DIGEST_SCHEMA,
7e1d4712 16 CHANGER_NAME_SCHEMA,
50bf10ad 17 LINUX_DRIVE_PATH_SCHEMA,
38ae42b1
DM
18 SLOT_ARRAY_SCHEMA,
19 EXPORT_SLOT_LIST_SCHEMA,
50bf10ad
DM
20 DriveListEntry,
21 ScsiTapeChanger,
43cfb3c3 22 LinuxTapeDrive,
50bf10ad 23 },
645a044b 24 tape::{
50bf10ad
DM
25 linux_tape_changer_list,
26 check_drive_path,
50bf10ad
DM
27 },
28};
29
30#[api(
314652a4 31 protected: true,
50bf10ad
DM
32 input: {
33 properties: {
34 name: {
7e1d4712 35 schema: CHANGER_NAME_SCHEMA,
50bf10ad
DM
36 },
37 path: {
38 schema: LINUX_DRIVE_PATH_SCHEMA,
39 },
38ae42b1
DM
40 "export-slots": {
41 schema: EXPORT_SLOT_LIST_SCHEMA,
42 optional: true,
43 },
50bf10ad
DM
44 },
45 },
46)]
47/// Create a new changer device
48pub fn create_changer(
49 name: String,
50 path: String,
38ae42b1 51 export_slots: Option<String>,
50bf10ad
DM
52) -> Result<(), Error> {
53
54 let _lock = config::drive::lock()?;
55
56 let (mut config, _digest) = config::drive::config()?;
57
58 let linux_changers = linux_tape_changer_list();
59
60 check_drive_path(&linux_changers, &path)?;
61
b03ec281
DC
62 let existing: Vec<ScsiTapeChanger> = config.convert_to_typed_array("changer")?;
63
64 for changer in existing {
65 if changer.name == name {
66 bail!("Entry '{}' already exists", name);
67 }
68
69 if changer.path == path {
70 bail!("Path '{}' already in use by '{}'", path, changer.name);
71 }
50bf10ad
DM
72 }
73
74 let item = ScsiTapeChanger {
75 name: name.clone(),
76 path,
38ae42b1 77 export_slots,
50bf10ad
DM
78 };
79
80 config.set_data(&name, "changer", &item)?;
81
82 config::drive::save_config(&config)?;
83
84 Ok(())
85}
86
87#[api(
88 input: {
89 properties: {
90 name: {
7e1d4712 91 schema: CHANGER_NAME_SCHEMA,
50bf10ad
DM
92 },
93 },
94 },
95 returns: {
96 type: ScsiTapeChanger,
97 },
98
99)]
100/// Get tape changer configuration
101pub fn get_config(
102 name: String,
103 _param: Value,
104 mut rpcenv: &mut dyn RpcEnvironment,
105) -> Result<ScsiTapeChanger, Error> {
106
107 let (config, digest) = config::drive::config()?;
108
109 let data: ScsiTapeChanger = config.lookup("changer", &name)?;
110
111 rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
112
113 Ok(data)
114}
115
116#[api(
117 input: {
118 properties: {},
119 },
120 returns: {
121 description: "The list of configured changers (with config digest).",
122 type: Array,
123 items: {
124 type: DriveListEntry,
125 },
126 },
127)]
128/// List changers
129pub fn list_changers(
130 _param: Value,
131 mut rpcenv: &mut dyn RpcEnvironment,
132) -> Result<Vec<DriveListEntry>, Error> {
133
134 let (config, digest) = config::drive::config()?;
135
50bf10ad
DM
136 let changer_list: Vec<ScsiTapeChanger> = config.convert_to_typed_array("changer")?;
137
138 let mut list = Vec::new();
139
140 for changer in changer_list {
740dc9d1 141 list.push(DriveListEntry {
50bf10ad
DM
142 name: changer.name,
143 path: changer.path.clone(),
144 changer: None,
d8792b88 145 changer_drivenum: None,
50bf10ad
DM
146 vendor: None,
147 model: None,
148 serial: None,
740dc9d1 149 });
50bf10ad
DM
150 }
151
152 rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
153 Ok(list)
154}
38ae42b1
DM
155#[api()]
156#[derive(Serialize, Deserialize)]
157#[allow(non_camel_case_types)]
158#[serde(rename_all = "kebab-case")]
159/// Deletable property name
160pub enum DeletableProperty {
161 /// Delete export-slots.
162 export_slots,
163}
50bf10ad
DM
164
165#[api(
314652a4 166 protected: true,
50bf10ad
DM
167 input: {
168 properties: {
169 name: {
7e1d4712 170 schema: CHANGER_NAME_SCHEMA,
50bf10ad
DM
171 },
172 path: {
173 schema: LINUX_DRIVE_PATH_SCHEMA,
174 optional: true,
175 },
38ae42b1
DM
176 "export-slots": {
177 schema: EXPORT_SLOT_LIST_SCHEMA,
178 optional: true,
179 },
180 delete: {
181 description: "List of properties to delete.",
182 type: Array,
183 optional: true,
184 items: {
185 type: DeletableProperty,
186 },
187 },
5ba83ed0
DM
188 digest: {
189 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
190 optional: true,
191 },
192 },
50bf10ad
DM
193 },
194)]
195/// Update a tape changer configuration
196pub fn update_changer(
197 name: String,
198 path: Option<String>,
38ae42b1
DM
199 export_slots: Option<String>,
200 delete: Option<Vec<DeletableProperty>>,
5ba83ed0 201 digest: Option<String>,
50bf10ad
DM
202 _param: Value,
203) -> Result<(), Error> {
204
205 let _lock = config::drive::lock()?;
206
5ba83ed0
DM
207 let (mut config, expected_digest) = config::drive::config()?;
208
209 if let Some(ref digest) = digest {
210 let digest = proxmox::tools::hex_to_digest(digest)?;
211 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
212 }
50bf10ad
DM
213
214 let mut data: ScsiTapeChanger = config.lookup("changer", &name)?;
215
38ae42b1
DM
216 if let Some(delete) = delete {
217 for delete_prop in delete {
218 match delete_prop {
219 DeletableProperty::export_slots => {
220 data.export_slots = None;
221 }
222 }
223 }
224 }
225
50bf10ad
DM
226 if let Some(path) = path {
227 let changers = linux_tape_changer_list();
228 check_drive_path(&changers, &path)?;
229 data.path = path;
230 }
231
38ae42b1
DM
232 if let Some(export_slots) = export_slots {
233 let slots: Value = parse_property_string(
234 &export_slots, &SLOT_ARRAY_SCHEMA
235 )?;
236 let mut slots: Vec<String> = slots
237 .as_array()
238 .unwrap()
239 .iter()
240 .map(|v| v.to_string())
241 .collect();
242 slots.sort();
243
244 if slots.is_empty() {
245 data.export_slots = None;
246 } else {
247 let slots = slots.join(",");
248 data.export_slots = Some(slots);
249 }
250 }
251
50bf10ad
DM
252 config.set_data(&name, "changer", &data)?;
253
254 config::drive::save_config(&config)?;
255
256 Ok(())
257}
258
259#[api(
314652a4 260 protected: true,
50bf10ad
DM
261 input: {
262 properties: {
263 name: {
7e1d4712 264 schema: CHANGER_NAME_SCHEMA,
50bf10ad
DM
265 },
266 },
267 },
268)]
269/// Delete a tape changer configuration
270pub fn delete_changer(name: String, _param: Value) -> Result<(), Error> {
271
272 let _lock = config::drive::lock()?;
273
274 let (mut config, _digest) = config::drive::config()?;
275
276 match config.sections.get(&name) {
277 Some((section_type, _)) => {
278 if section_type != "changer" {
279 bail!("Entry '{}' exists, but is not a changer device", name);
280 }
281 config.sections.remove(&name);
282 },
283 None => bail!("Delete changer '{}' failed - no such entry", name),
284 }
285
43cfb3c3
DM
286 let drive_list: Vec<LinuxTapeDrive> = config.convert_to_typed_array("linux")?;
287 for drive in drive_list {
288 if let Some(changer) = drive.changer {
289 if changer == name {
290 bail!("Delete changer '{}' failed - used by drive '{}'", name, drive.name);
291 }
292 }
293 }
294
50bf10ad
DM
295 config::drive::save_config(&config)?;
296
297 Ok(())
298}
299
50bf10ad
DM
300const ITEM_ROUTER: Router = Router::new()
301 .get(&API_METHOD_GET_CONFIG)
302 .put(&API_METHOD_UPDATE_CHANGER)
303 .delete(&API_METHOD_DELETE_CHANGER);
304
305
306pub const ROUTER: Router = Router::new()
307 .get(&API_METHOD_LIST_CHANGERS)
308 .post(&API_METHOD_CREATE_CHANGER)
309 .match_all("name", &ITEM_ROUTER);