]> git.proxmox.com Git - proxmox-backup.git/blob - src/api2/config/changer.rs
api2/config/{drive, changer}: prevent adding same device multiple times
[proxmox-backup.git] / src / api2 / config / changer.rs
1 use anyhow::{bail, Error};
2 use ::serde::{Deserialize, Serialize};
3 use serde_json::Value;
4
5 use proxmox::api::{
6 api,
7 Router,
8 RpcEnvironment,
9 schema::parse_property_string,
10 };
11
12 use crate::{
13 config,
14 api2::types::{
15 PROXMOX_CONFIG_DIGEST_SCHEMA,
16 CHANGER_NAME_SCHEMA,
17 LINUX_DRIVE_PATH_SCHEMA,
18 SLOT_ARRAY_SCHEMA,
19 EXPORT_SLOT_LIST_SCHEMA,
20 DriveListEntry,
21 ScsiTapeChanger,
22 LinuxTapeDrive,
23 },
24 tape::{
25 linux_tape_changer_list,
26 check_drive_path,
27 },
28 };
29
30 #[api(
31 protected: true,
32 input: {
33 properties: {
34 name: {
35 schema: CHANGER_NAME_SCHEMA,
36 },
37 path: {
38 schema: LINUX_DRIVE_PATH_SCHEMA,
39 },
40 "export-slots": {
41 schema: EXPORT_SLOT_LIST_SCHEMA,
42 optional: true,
43 },
44 },
45 },
46 )]
47 /// Create a new changer device
48 pub fn create_changer(
49 name: String,
50 path: String,
51 export_slots: Option<String>,
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
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 }
72 }
73
74 let item = ScsiTapeChanger {
75 name: name.clone(),
76 path,
77 export_slots,
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: {
91 schema: CHANGER_NAME_SCHEMA,
92 },
93 },
94 },
95 returns: {
96 type: ScsiTapeChanger,
97 },
98
99 )]
100 /// Get tape changer configuration
101 pub 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
129 pub 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
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 {
141 list.push(DriveListEntry {
142 name: changer.name,
143 path: changer.path.clone(),
144 changer: None,
145 changer_drivenum: None,
146 vendor: None,
147 model: None,
148 serial: None,
149 });
150 }
151
152 rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
153 Ok(list)
154 }
155 #[api()]
156 #[derive(Serialize, Deserialize)]
157 #[allow(non_camel_case_types)]
158 #[serde(rename_all = "kebab-case")]
159 /// Deletable property name
160 pub enum DeletableProperty {
161 /// Delete export-slots.
162 export_slots,
163 }
164
165 #[api(
166 protected: true,
167 input: {
168 properties: {
169 name: {
170 schema: CHANGER_NAME_SCHEMA,
171 },
172 path: {
173 schema: LINUX_DRIVE_PATH_SCHEMA,
174 optional: true,
175 },
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 },
188 digest: {
189 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
190 optional: true,
191 },
192 },
193 },
194 )]
195 /// Update a tape changer configuration
196 pub fn update_changer(
197 name: String,
198 path: Option<String>,
199 export_slots: Option<String>,
200 delete: Option<Vec<DeletableProperty>>,
201 digest: Option<String>,
202 _param: Value,
203 ) -> Result<(), Error> {
204
205 let _lock = config::drive::lock()?;
206
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 }
213
214 let mut data: ScsiTapeChanger = config.lookup("changer", &name)?;
215
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
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
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
252 config.set_data(&name, "changer", &data)?;
253
254 config::drive::save_config(&config)?;
255
256 Ok(())
257 }
258
259 #[api(
260 protected: true,
261 input: {
262 properties: {
263 name: {
264 schema: CHANGER_NAME_SCHEMA,
265 },
266 },
267 },
268 )]
269 /// Delete a tape changer configuration
270 pub 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
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
295 config::drive::save_config(&config)?;
296
297 Ok(())
298 }
299
300 const 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
306 pub const ROUTER: Router = Router::new()
307 .get(&API_METHOD_LIST_CHANGERS)
308 .post(&API_METHOD_CREATE_CHANGER)
309 .match_all("name", &ITEM_ROUTER);