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