]> git.proxmox.com Git - proxmox-backup.git/blob - src/tape/changer/online_status_map.rs
tree-wide: fix needless borrows
[proxmox-backup.git] / src / tape / changer / online_status_map.rs
1 use std::path::Path;
2 use std::collections::{HashMap, HashSet};
3
4 use anyhow::{bail, Error};
5
6 use proxmox_section_config::SectionConfigData;
7 use proxmox_uuid::Uuid;
8
9 use pbs_api_types::{VirtualTapeDrive, ScsiTapeChanger};
10 use pbs_tape::{ElementStatus, MtxStatus};
11
12 use crate::tape::Inventory;
13 use crate::tape::changer::{MediaChange, ScsiMediaChange};
14
15 /// Helper to update media online status
16 ///
17 /// A tape media is considered online if it is accessible by a changer
18 /// device. This class can store the list of available changes,
19 /// together with the accessible media ids.
20 pub struct OnlineStatusMap {
21 map: HashMap<String, Option<HashSet<Uuid>>>,
22 changer_map: HashMap<Uuid, String>,
23 }
24
25 impl OnlineStatusMap {
26
27 /// Creates a new instance with one map entry for each configured
28 /// changer (or 'VirtualTapeDrive', which has an internal
29 /// changer). The map entry is set to 'None' to indicate that we
30 /// do not have information about the online status.
31 pub fn new(config: &SectionConfigData) -> Result<Self, Error> {
32
33 let mut map = HashMap::new();
34
35 let changers: Vec<ScsiTapeChanger> = config.convert_to_typed_array("changer")?;
36 for changer in changers {
37 map.insert(changer.name.clone(), None);
38 }
39
40 let vtapes: Vec<VirtualTapeDrive> = config.convert_to_typed_array("virtual")?;
41 for vtape in vtapes {
42 map.insert(vtape.name.clone(), None);
43 }
44
45 Ok(Self { map, changer_map: HashMap::new() })
46 }
47
48 /// Returns the assiciated changer name for a media.
49 pub fn lookup_changer(&self, uuid: &Uuid) -> Option<&String> {
50 self.changer_map.get(uuid)
51 }
52
53 /// Returns the map which assiciates media uuids with changer names.
54 pub fn changer_map(&self) -> &HashMap<Uuid, String> {
55 &self.changer_map
56 }
57
58 /// Returns the set of online media for the specified changer.
59 pub fn online_map(&self, changer_name: &str) -> Option<&Option<HashSet<Uuid>>> {
60 self.map.get(changer_name)
61 }
62
63 /// Update the online set for the specified changer
64 pub fn update_online_status(&mut self, changer_name: &str, online_set: HashSet<Uuid>) -> Result<(), Error> {
65
66 match self.map.get(changer_name) {
67 None => bail!("no such changer '{}' device", changer_name),
68 Some(None) => { /* Ok */ },
69 Some(Some(_)) => {
70 // do not allow updates to keep self.changer_map consistent
71 bail!("update_online_status '{}' called twice", changer_name);
72 }
73 }
74
75 for uuid in online_set.iter() {
76 self.changer_map.insert(uuid.clone(), changer_name.to_string());
77 }
78
79 self.map.insert(changer_name.to_string(), Some(online_set));
80
81 Ok(())
82 }
83 }
84
85 /// Extract the list of online media from MtxStatus
86 ///
87 /// Returns a HashSet containing all found media Uuid. This only
88 /// returns media found in Inventory.
89 pub fn mtx_status_to_online_set(status: &MtxStatus, inventory: &Inventory) -> HashSet<Uuid> {
90
91 let mut online_set = HashSet::new();
92
93 for drive_status in status.drives.iter() {
94 if let ElementStatus::VolumeTag(ref label_text) = drive_status.status {
95 if let Some(media_id) = inventory.find_media_by_label_text(label_text) {
96 online_set.insert(media_id.label.uuid.clone());
97 }
98 }
99 }
100
101 for slot_info in status.slots.iter() {
102 if slot_info.import_export { continue; }
103 if let ElementStatus::VolumeTag(ref label_text) = slot_info.status {
104 if let Some(media_id) = inventory.find_media_by_label_text(label_text) {
105 online_set.insert(media_id.label.uuid.clone());
106 }
107 }
108 }
109
110 online_set
111 }
112
113 /// Update online media status
114 ///
115 /// For a single 'changer', or else simply ask all changer devices.
116 pub fn update_online_status(state_path: &Path, changer: Option<&str>) -> Result<OnlineStatusMap, Error> {
117
118 let (config, _digest) = pbs_config::drive::config()?;
119
120 let mut inventory = Inventory::load(state_path)?;
121
122 let changers: Vec<ScsiTapeChanger> = config.convert_to_typed_array("changer")?;
123
124 let mut map = OnlineStatusMap::new(&config)?;
125
126 let mut found_changer = false;
127
128 for mut changer_config in changers {
129 if let Some(changer) = changer {
130 if changer != &changer_config.name {
131 continue;
132 }
133 found_changer = true;
134 }
135 let status = match changer_config.status(false) {
136 Ok(status) => status,
137 Err(err) => {
138 eprintln!("unable to get changer '{}' status - {}", changer_config.name, err);
139 continue;
140 }
141 };
142
143 let online_set = mtx_status_to_online_set(&status, &inventory);
144 map.update_online_status(&changer_config.name, online_set)?;
145 }
146
147 let vtapes: Vec<VirtualTapeDrive> = config.convert_to_typed_array("virtual")?;
148 for mut vtape in vtapes {
149 if let Some(changer) = changer {
150 if changer != &vtape.name {
151 continue;
152 }
153 found_changer = true;
154 }
155
156 let media_list = match vtape.online_media_label_texts() {
157 Ok(media_list) => media_list,
158 Err(err) => {
159 eprintln!("unable to get changer '{}' status - {}", vtape.name, err);
160 continue;
161 }
162 };
163
164 let mut online_set = HashSet::new();
165 for label_text in media_list {
166 if let Some(media_id) = inventory.find_media_by_label_text(&label_text) {
167 online_set.insert(media_id.label.uuid.clone());
168 }
169 }
170 map.update_online_status(&vtape.name, online_set)?;
171 }
172
173 if let Some(changer) = changer {
174 if !found_changer {
175 bail!("update_online_status failed - no such changer '{}'", changer);
176 }
177 }
178
179 inventory.update_online_status(&map)?;
180
181 Ok(map)
182 }
183
184 /// Update online media status with data from a single changer device
185 pub fn update_changer_online_status(
186 drive_config: &SectionConfigData,
187 inventory: &mut Inventory,
188 changer_name: &str,
189 label_text_list: &[String],
190 ) -> Result<(), Error> {
191
192 let mut online_map = OnlineStatusMap::new(drive_config)?;
193 let mut online_set = HashSet::new();
194 for label_text in label_text_list.iter() {
195 if let Some(media_id) = inventory.find_media_by_label_text(label_text) {
196 online_set.insert(media_id.label.uuid.clone());
197 }
198 }
199 online_map.update_online_status(changer_name, online_set)?;
200 inventory.update_online_status(&online_map)?;
201
202 Ok(())
203 }