]>
Commit | Line | Data |
---|---|---|
8be48ddf | 1 | use std::collections::HashMap; |
cafd51bf DM |
2 | use std::path::Path; |
3 | ||
5d908606 DM |
4 | use anyhow::Error; |
5 | use serde_json::Value; | |
6 | ||
6ef1b649 WB |
7 | use proxmox_schema::api; |
8 | use proxmox_router::{list_subdirs_api_method, Permission, Router, RpcEnvironment, SubdirMap}; | |
5d908606 | 9 | |
8cc3760e DM |
10 | use pbs_api_types::{ |
11 | Authid, ChangerListEntry, LtoTapeDrive, MtxEntryKind, MtxStatusEntry, ScsiTapeChanger, | |
12 | CHANGER_NAME_SCHEMA, PRIV_TAPE_AUDIT, PRIV_TAPE_READ, | |
13 | }; | |
ba3d7e19 | 14 | use pbs_config::CachedUserInfo; |
048b43af DM |
15 | use pbs_tape::{ |
16 | ElementStatus, | |
17 | linux_list_drives::{lookup_device_identification, linux_tape_changer_list}, | |
18 | }; | |
8cc3760e | 19 | |
5d908606 | 20 | use crate::{ |
5d908606 | 21 | tape::{ |
cafd51bf | 22 | TAPE_STATUS_DIR, |
cafd51bf | 23 | Inventory, |
37796ff7 DM |
24 | changer::{ |
25 | OnlineStatusMap, | |
697c41c5 | 26 | ScsiMediaChange, |
37796ff7 | 27 | mtx_status_to_online_set, |
37796ff7 | 28 | }, |
8be48ddf | 29 | drive::get_tape_device_state, |
5d908606 DM |
30 | }, |
31 | }; | |
32 | ||
33 | ||
34 | #[api( | |
35 | input: { | |
36 | properties: { | |
37 | name: { | |
7e1d4712 | 38 | schema: CHANGER_NAME_SCHEMA, |
5d908606 | 39 | }, |
4188fd59 DM |
40 | cache: { |
41 | description: "Use cached value.", | |
42 | optional: true, | |
43 | default: true, | |
44 | }, | |
5d908606 DM |
45 | }, |
46 | }, | |
47 | returns: { | |
48 | description: "A status entry for each drive and slot.", | |
49 | type: Array, | |
50 | items: { | |
51 | type: MtxStatusEntry, | |
52 | }, | |
53 | }, | |
b4975d31 DM |
54 | access: { |
55 | permission: &Permission::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_AUDIT, false), | |
56 | }, | |
5d908606 DM |
57 | )] |
58 | /// Get tape changer status | |
4188fd59 DM |
59 | pub async fn get_status( |
60 | name: String, | |
61 | cache: bool, | |
62 | ) -> Result<Vec<MtxStatusEntry>, Error> { | |
5d908606 | 63 | |
1ce8e905 | 64 | let (config, _digest) = pbs_config::drive::config()?; |
5d908606 | 65 | |
697c41c5 | 66 | let mut changer_config: ScsiTapeChanger = config.lookup("changer", &name)?; |
5d908606 | 67 | |
42cb9bd6 | 68 | let status = tokio::task::spawn_blocking(move || { |
4188fd59 | 69 | changer_config.status(cache) |
42cb9bd6 | 70 | }).await??; |
5d908606 | 71 | |
cafd51bf | 72 | let state_path = Path::new(TAPE_STATUS_DIR); |
cfae8f06 | 73 | let mut inventory = Inventory::load(state_path)?; |
5d908606 DM |
74 | |
75 | let mut map = OnlineStatusMap::new(&config)?; | |
76 | let online_set = mtx_status_to_online_set(&status, &inventory); | |
77 | map.update_online_status(&name, online_set)?; | |
78 | ||
cfae8f06 | 79 | inventory.update_online_status(&map)?; |
5d908606 | 80 | |
a79082a0 | 81 | let drive_list: Vec<LtoTapeDrive> = config.convert_to_typed_array("lto")?; |
8be48ddf DC |
82 | let mut drive_map: HashMap<u64, String> = HashMap::new(); |
83 | ||
84 | for drive in drive_list { | |
85 | if let Some(changer) = drive.changer { | |
86 | if changer != name { | |
87 | continue; | |
88 | } | |
89 | let num = drive.changer_drivenum.unwrap_or(0); | |
90 | drive_map.insert(num, drive.name.clone()); | |
91 | } | |
92 | } | |
93 | ||
5d908606 DM |
94 | let mut list = Vec::new(); |
95 | ||
96 | for (id, drive_status) in status.drives.iter().enumerate() { | |
8be48ddf DC |
97 | let mut state = None; |
98 | if let Some(drive) = drive_map.get(&(id as u64)) { | |
99 | state = get_tape_device_state(&config, &drive)?; | |
100 | } | |
5d908606 DM |
101 | let entry = MtxStatusEntry { |
102 | entry_kind: MtxEntryKind::Drive, | |
103 | entry_id: id as u64, | |
8446fbca | 104 | label_text: match &drive_status.status { |
5d908606 DM |
105 | ElementStatus::Empty => None, |
106 | ElementStatus::Full => Some(String::new()), | |
107 | ElementStatus::VolumeTag(tag) => Some(tag.to_string()), | |
108 | }, | |
109 | loaded_slot: drive_status.loaded_slot, | |
8be48ddf | 110 | state, |
5d908606 DM |
111 | }; |
112 | list.push(entry); | |
113 | } | |
114 | ||
697c41c5 | 115 | for (id, slot_info) in status.slots.iter().enumerate() { |
5d908606 | 116 | let entry = MtxStatusEntry { |
697c41c5 | 117 | entry_kind: if slot_info.import_export { |
e0362b0d DM |
118 | MtxEntryKind::ImportExport |
119 | } else { | |
120 | MtxEntryKind::Slot | |
121 | }, | |
5d908606 | 122 | entry_id: id as u64 + 1, |
697c41c5 | 123 | label_text: match &slot_info.status { |
5d908606 DM |
124 | ElementStatus::Empty => None, |
125 | ElementStatus::Full => Some(String::new()), | |
126 | ElementStatus::VolumeTag(tag) => Some(tag.to_string()), | |
127 | }, | |
128 | loaded_slot: None, | |
8be48ddf | 129 | state: None, |
5d908606 DM |
130 | }; |
131 | list.push(entry); | |
132 | } | |
133 | ||
134 | Ok(list) | |
135 | } | |
136 | ||
137 | #[api( | |
138 | input: { | |
139 | properties: { | |
140 | name: { | |
7e1d4712 | 141 | schema: CHANGER_NAME_SCHEMA, |
5d908606 DM |
142 | }, |
143 | from: { | |
144 | description: "Source slot number", | |
145 | minimum: 1, | |
146 | }, | |
147 | to: { | |
148 | description: "Destination slot number", | |
149 | minimum: 1, | |
150 | }, | |
151 | }, | |
152 | }, | |
b4975d31 DM |
153 | access: { |
154 | permission: &Permission::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_READ, false), | |
155 | }, | |
5d908606 DM |
156 | )] |
157 | /// Transfers media from one slot to another | |
42cb9bd6 | 158 | pub async fn transfer( |
5d908606 DM |
159 | name: String, |
160 | from: u64, | |
161 | to: u64, | |
162 | ) -> Result<(), Error> { | |
163 | ||
1ce8e905 | 164 | let (config, _digest) = pbs_config::drive::config()?; |
5d908606 | 165 | |
697c41c5 | 166 | let mut changer_config: ScsiTapeChanger = config.lookup("changer", &name)?; |
5d908606 | 167 | |
42cb9bd6 | 168 | tokio::task::spawn_blocking(move || { |
cbd98993 | 169 | changer_config.transfer(from, to)?; |
cbd98993 | 170 | Ok(()) |
42cb9bd6 | 171 | }).await? |
5d908606 DM |
172 | } |
173 | ||
740dc9d1 DC |
174 | #[api( |
175 | input: { | |
176 | properties: {}, | |
177 | }, | |
178 | returns: { | |
179 | description: "The list of configured changers with model information.", | |
180 | type: Array, | |
181 | items: { | |
b5b99a52 | 182 | type: ChangerListEntry, |
740dc9d1 DC |
183 | }, |
184 | }, | |
8cd63df0 DM |
185 | access: { |
186 | description: "List configured tape changer filtered by Tape.Audit privileges", | |
187 | permission: &Permission::Anybody, | |
188 | }, | |
740dc9d1 DC |
189 | )] |
190 | /// List changers | |
191 | pub fn list_changers( | |
192 | _param: Value, | |
8cd63df0 | 193 | rpcenv: &mut dyn RpcEnvironment, |
b5b99a52 | 194 | ) -> Result<Vec<ChangerListEntry>, Error> { |
8cd63df0 DM |
195 | let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; |
196 | let user_info = CachedUserInfo::new()?; | |
740dc9d1 | 197 | |
1ce8e905 | 198 | let (config, _digest) = pbs_config::drive::config()?; |
740dc9d1 DC |
199 | |
200 | let linux_changers = linux_tape_changer_list(); | |
201 | ||
202 | let changer_list: Vec<ScsiTapeChanger> = config.convert_to_typed_array("changer")?; | |
203 | ||
204 | let mut list = Vec::new(); | |
205 | ||
206 | for changer in changer_list { | |
8cd63df0 DM |
207 | let privs = user_info.lookup_privs(&auth_id, &["tape", "changer", &changer.name]); |
208 | if (privs & PRIV_TAPE_AUDIT) == 0 { | |
209 | continue; | |
210 | } | |
211 | ||
b5b99a52 DM |
212 | let info = lookup_device_identification(&linux_changers, &changer.path); |
213 | let entry = ChangerListEntry { config: changer, info }; | |
740dc9d1 DC |
214 | list.push(entry); |
215 | } | |
216 | Ok(list) | |
217 | } | |
218 | ||
5d908606 DM |
219 | const SUBDIRS: SubdirMap = &[ |
220 | ( | |
740dc9d1 DC |
221 | "status", |
222 | &Router::new() | |
223 | .get(&API_METHOD_GET_STATUS) | |
224 | ), | |
225 | ( | |
226 | "transfer", | |
5d908606 | 227 | &Router::new() |
740dc9d1 | 228 | .post(&API_METHOD_TRANSFER) |
5d908606 DM |
229 | ), |
230 | ]; | |
231 | ||
740dc9d1 | 232 | const ITEM_ROUTER: Router = Router::new() |
5d908606 | 233 | .get(&list_subdirs_api_method!(SUBDIRS)) |
740dc9d1 DC |
234 | .subdirs(&SUBDIRS); |
235 | ||
236 | pub const ROUTER: Router = Router::new() | |
237 | .get(&API_METHOD_LIST_CHANGERS) | |
238 | .match_all("name", &ITEM_ROUTER); |