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