]> git.proxmox.com Git - proxmox-backup.git/blob - src/api2/tape/changer.rs
update to first proxmox crate split
[proxmox-backup.git] / src / api2 / tape / changer.rs
1 use std::collections::HashMap;
2 use std::path::Path;
3
4 use anyhow::Error;
5 use serde_json::Value;
6
7 use proxmox_schema::api;
8 use proxmox_router::{list_subdirs_api_method, Permission, Router, RpcEnvironment, SubdirMap};
9
10 use pbs_api_types::{
11 Authid, ChangerListEntry, LtoTapeDrive, MtxEntryKind, MtxStatusEntry, ScsiTapeChanger,
12 CHANGER_NAME_SCHEMA, PRIV_TAPE_AUDIT, PRIV_TAPE_READ,
13 };
14 use pbs_config::CachedUserInfo;
15 use pbs_tape::{
16 ElementStatus,
17 linux_list_drives::{lookup_device_identification, linux_tape_changer_list},
18 };
19
20 use crate::{
21 tape::{
22 TAPE_STATUS_DIR,
23 Inventory,
24 changer::{
25 OnlineStatusMap,
26 ScsiMediaChange,
27 mtx_status_to_online_set,
28 },
29 drive::get_tape_device_state,
30 },
31 };
32
33
34 #[api(
35 input: {
36 properties: {
37 name: {
38 schema: CHANGER_NAME_SCHEMA,
39 },
40 cache: {
41 description: "Use cached value.",
42 optional: true,
43 default: true,
44 },
45 },
46 },
47 returns: {
48 description: "A status entry for each drive and slot.",
49 type: Array,
50 items: {
51 type: MtxStatusEntry,
52 },
53 },
54 access: {
55 permission: &Permission::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_AUDIT, false),
56 },
57 )]
58 /// Get tape changer status
59 pub async fn get_status(
60 name: String,
61 cache: bool,
62 ) -> Result<Vec<MtxStatusEntry>, Error> {
63
64 let (config, _digest) = pbs_config::drive::config()?;
65
66 let mut changer_config: ScsiTapeChanger = config.lookup("changer", &name)?;
67
68 let status = tokio::task::spawn_blocking(move || {
69 changer_config.status(cache)
70 }).await??;
71
72 let state_path = Path::new(TAPE_STATUS_DIR);
73 let mut inventory = Inventory::load(state_path)?;
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
79 inventory.update_online_status(&map)?;
80
81 let drive_list: Vec<LtoTapeDrive> = config.convert_to_typed_array("lto")?;
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
94 let mut list = Vec::new();
95
96 for (id, drive_status) in status.drives.iter().enumerate() {
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 }
101 let entry = MtxStatusEntry {
102 entry_kind: MtxEntryKind::Drive,
103 entry_id: id as u64,
104 label_text: match &drive_status.status {
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,
110 state,
111 };
112 list.push(entry);
113 }
114
115 for (id, slot_info) in status.slots.iter().enumerate() {
116 let entry = MtxStatusEntry {
117 entry_kind: if slot_info.import_export {
118 MtxEntryKind::ImportExport
119 } else {
120 MtxEntryKind::Slot
121 },
122 entry_id: id as u64 + 1,
123 label_text: match &slot_info.status {
124 ElementStatus::Empty => None,
125 ElementStatus::Full => Some(String::new()),
126 ElementStatus::VolumeTag(tag) => Some(tag.to_string()),
127 },
128 loaded_slot: None,
129 state: None,
130 };
131 list.push(entry);
132 }
133
134 Ok(list)
135 }
136
137 #[api(
138 input: {
139 properties: {
140 name: {
141 schema: CHANGER_NAME_SCHEMA,
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 },
153 access: {
154 permission: &Permission::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_READ, false),
155 },
156 )]
157 /// Transfers media from one slot to another
158 pub async fn transfer(
159 name: String,
160 from: u64,
161 to: u64,
162 ) -> Result<(), Error> {
163
164 let (config, _digest) = pbs_config::drive::config()?;
165
166 let mut changer_config: ScsiTapeChanger = config.lookup("changer", &name)?;
167
168 tokio::task::spawn_blocking(move || {
169 changer_config.transfer(from, to)?;
170 Ok(())
171 }).await?
172 }
173
174 #[api(
175 input: {
176 properties: {},
177 },
178 returns: {
179 description: "The list of configured changers with model information.",
180 type: Array,
181 items: {
182 type: ChangerListEntry,
183 },
184 },
185 access: {
186 description: "List configured tape changer filtered by Tape.Audit privileges",
187 permission: &Permission::Anybody,
188 },
189 )]
190 /// List changers
191 pub fn list_changers(
192 _param: Value,
193 rpcenv: &mut dyn RpcEnvironment,
194 ) -> Result<Vec<ChangerListEntry>, Error> {
195 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
196 let user_info = CachedUserInfo::new()?;
197
198 let (config, _digest) = pbs_config::drive::config()?;
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 {
207 let privs = user_info.lookup_privs(&auth_id, &["tape", "changer", &changer.name]);
208 if (privs & PRIV_TAPE_AUDIT) == 0 {
209 continue;
210 }
211
212 let info = lookup_device_identification(&linux_changers, &changer.path);
213 let entry = ChangerListEntry { config: changer, info };
214 list.push(entry);
215 }
216 Ok(list)
217 }
218
219 const SUBDIRS: SubdirMap = &[
220 (
221 "status",
222 &Router::new()
223 .get(&API_METHOD_GET_STATUS)
224 ),
225 (
226 "transfer",
227 &Router::new()
228 .post(&API_METHOD_TRANSFER)
229 ),
230 ];
231
232 const ITEM_ROUTER: Router = Router::new()
233 .get(&list_subdirs_api_method!(SUBDIRS))
234 .subdirs(&SUBDIRS);
235
236 pub const ROUTER: Router = Router::new()
237 .get(&API_METHOD_LIST_CHANGERS)
238 .match_all("name", &ITEM_ROUTER);