]> git.proxmox.com Git - proxmox-backup.git/blob - src/tape/media_state_database.rs
tape: add media state database
[proxmox-backup.git] / src / tape / media_state_database.rs
1 use std::path::{Path, PathBuf};
2 use std::collections::BTreeMap;
3
4 use anyhow::Error;
5 use ::serde::{Deserialize, Serialize};
6 use serde_json::json;
7
8 use proxmox::tools::{
9 Uuid,
10 fs::{
11 open_file_locked,
12 replace_file,
13 file_get_json,
14 CreateOptions,
15 },
16 };
17
18 use crate::{
19 tape::{
20 OnlineStatusMap,
21 },
22 api2::types::{
23 MediaStatus,
24 },
25 };
26
27 #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
28 /// Media location
29 pub enum MediaLocation {
30 /// Ready for use (inside tape library)
31 Online(String),
32 /// Local available, but need to be mounted (insert into tape
33 /// drive)
34 Offline,
35 /// Media is inside a Vault
36 Vault(String),
37 }
38
39 #[derive(Serialize,Deserialize)]
40 struct MediaStateEntry {
41 u: Uuid,
42 #[serde(skip_serializing_if="Option::is_none")]
43 l: Option<MediaLocation>,
44 #[serde(skip_serializing_if="Option::is_none")]
45 s: Option<MediaStatus>,
46 }
47
48 impl MediaStateEntry {
49 fn new(uuid: Uuid) -> Self {
50 MediaStateEntry { u: uuid, l: None, s: None }
51 }
52 }
53
54 /// Stores MediaLocation and MediaState persistently
55 pub struct MediaStateDatabase {
56
57 map: BTreeMap<Uuid, MediaStateEntry>,
58
59 database_path: PathBuf,
60 lockfile_path: PathBuf,
61 }
62
63 impl MediaStateDatabase {
64
65 pub const MEDIA_STATUS_DATABASE_FILENAME: &'static str = "media-status-db.json";
66 pub const MEDIA_STATUS_DATABASE_LOCKFILE: &'static str = ".media-status-db.lck";
67
68
69 /// Lock the database
70 pub fn lock(&self) -> Result<std::fs::File, Error> {
71 open_file_locked(&self.lockfile_path, std::time::Duration::new(10, 0), true)
72 }
73
74 /// Returns status and location with reasonable defaults.
75 ///
76 /// Default status is 'MediaStatus::Unknown'.
77 /// Default location is 'MediaLocation::Offline'.
78 pub fn status_and_location(&self, uuid: &Uuid) -> (MediaStatus, MediaLocation) {
79
80 match self.map.get(uuid) {
81 None => {
82 // no info stored - assume media is writable/offline
83 (MediaStatus::Unknown, MediaLocation::Offline)
84 }
85 Some(entry) => {
86 let location = entry.l.clone().unwrap_or(MediaLocation::Offline);
87 let status = entry.s.unwrap_or(MediaStatus::Unknown);
88 (status, location)
89 }
90 }
91 }
92
93 fn load_media_db(path: &Path) -> Result<BTreeMap<Uuid, MediaStateEntry>, Error> {
94
95 let data = file_get_json(path, Some(json!([])))?;
96 let list: Vec<MediaStateEntry> = serde_json::from_value(data)?;
97
98 let mut map = BTreeMap::new();
99 for entry in list.into_iter() {
100 map.insert(entry.u.clone(), entry);
101 }
102
103 Ok(map)
104 }
105
106 /// Load the database into memory
107 pub fn load(base_path: &Path) -> Result<MediaStateDatabase, Error> {
108
109 let mut database_path = base_path.to_owned();
110 database_path.push(Self::MEDIA_STATUS_DATABASE_FILENAME);
111
112 let mut lockfile_path = base_path.to_owned();
113 lockfile_path.push(Self::MEDIA_STATUS_DATABASE_LOCKFILE);
114
115 Ok(MediaStateDatabase {
116 map: Self::load_media_db(&database_path)?,
117 database_path,
118 lockfile_path,
119 })
120 }
121
122 /// Lock database, reload database, set status to Full, store database
123 pub fn set_media_status_full(&mut self, uuid: &Uuid) -> Result<(), Error> {
124 let _lock = self.lock()?;
125 self.map = Self::load_media_db(&self.database_path)?;
126 let entry = self.map.entry(uuid.clone()).or_insert(MediaStateEntry::new(uuid.clone()));
127 entry.s = Some(MediaStatus::Full);
128 self.store()
129 }
130
131 /// Update online status
132 pub fn update_online_status(&mut self, online_map: &OnlineStatusMap) -> Result<(), Error> {
133 let _lock = self.lock()?;
134 self.map = Self::load_media_db(&self.database_path)?;
135
136 for (_uuid, entry) in self.map.iter_mut() {
137 if let Some(changer_name) = online_map.lookup_changer(&entry.u) {
138 entry.l = Some(MediaLocation::Online(changer_name.to_string()));
139 } else {
140 if let Some(MediaLocation::Online(ref changer_name)) = entry.l {
141 match online_map.online_map(changer_name) {
142 None => {
143 // no such changer device
144 entry.l = Some(MediaLocation::Offline);
145 }
146 Some(None) => {
147 // got no info - do nothing
148 }
149 Some(Some(_)) => {
150 // media changer changed
151 entry.l = Some(MediaLocation::Offline);
152 }
153 }
154 }
155 }
156 }
157
158 for (uuid, changer_name) in online_map.changer_map() {
159 if self.map.contains_key(uuid) { continue; }
160 let mut entry = MediaStateEntry::new(uuid.clone());
161 entry.l = Some(MediaLocation::Online(changer_name.to_string()));
162 self.map.insert(uuid.clone(), entry);
163 }
164
165 self.store()
166 }
167
168 /// Lock database, reload database, set status to Damaged, store database
169 pub fn set_media_status_damaged(&mut self, uuid: &Uuid) -> Result<(), Error> {
170 let _lock = self.lock()?;
171 self.map = Self::load_media_db(&self.database_path)?;
172 let entry = self.map.entry(uuid.clone()).or_insert(MediaStateEntry::new(uuid.clone()));
173 entry.s = Some(MediaStatus::Damaged);
174 self.store()
175 }
176
177 /// Lock database, reload database, set status to None, store database
178 pub fn clear_media_status(&mut self, uuid: &Uuid) -> Result<(), Error> {
179 let _lock = self.lock()?;
180 self.map = Self::load_media_db(&self.database_path)?;
181 let entry = self.map.entry(uuid.clone()).or_insert(MediaStateEntry::new(uuid.clone()));
182 entry.s = None ;
183 self.store()
184 }
185
186 /// Lock database, reload database, set location to vault, store database
187 pub fn set_media_location_vault(&mut self, uuid: &Uuid, vault: &str) -> Result<(), Error> {
188 let _lock = self.lock()?;
189 self.map = Self::load_media_db(&self.database_path)?;
190 let entry = self.map.entry(uuid.clone()).or_insert(MediaStateEntry::new(uuid.clone()));
191 entry.l = Some(MediaLocation::Vault(vault.to_string()));
192 self.store()
193 }
194
195 /// Lock database, reload database, set location to offline, store database
196 pub fn set_media_location_offline(&mut self, uuid: &Uuid) -> Result<(), Error> {
197 let _lock = self.lock()?;
198 self.map = Self::load_media_db(&self.database_path)?;
199 let entry = self.map.entry(uuid.clone()).or_insert(MediaStateEntry::new(uuid.clone()));
200 entry.l = Some(MediaLocation::Offline);
201 self.store()
202 }
203
204 fn store(&self) -> Result<(), Error> {
205
206 let mut list = Vec::new();
207 for entry in self.map.values() {
208 list.push(entry);
209 }
210
211 let raw = serde_json::to_string_pretty(&serde_json::to_value(list)?)?;
212
213 let backup_user = crate::backup::backup_user()?;
214 let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640);
215 let options = CreateOptions::new()
216 .perm(mode)
217 .owner(backup_user.uid)
218 .group(backup_user.gid);
219
220 replace_file(&self.database_path, raw.as_bytes(), options)?;
221
222 Ok(())
223 }
224 }