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