]> git.proxmox.com Git - proxmox-backup.git/blame - src/tape/media_pool.rs
tree-wide: fix needless borrows
[proxmox-backup.git] / src / tape / media_pool.rs
CommitLineData
c4d8542e
DM
1//! Media Pool
2//!
3//! A set of backup medias.
4//!
5//! This struct manages backup media state during backup. The main
d1d74c43 6//! purpose is to allocate media sets and assign new tapes to it.
c4d8542e
DM
7//!
8//!
9
1057b1f5 10use std::path::{PathBuf, Path};
30316192 11
c4d8542e 12use anyhow::{bail, Error};
b2065dc7 13use serde::{Deserialize, Serialize};
c4d8542e 14
6ef1b649 15use proxmox_uuid::Uuid;
c4d8542e 16
6227654a
DM
17use pbs_api_types::{
18 Fingerprint, MediaStatus, MediaLocation, MediaSetPolicy, RetentionPolicy,
19 MediaPoolConfig,
20};
21211748 21use pbs_config::BackupLockGuard;
b2065dc7 22
6227654a
DM
23use crate::tape::{
24 MediaId,
25 MediaSet,
26 Inventory,
27 MediaCatalog,
28 lock_media_set,
29 lock_media_pool,
30 lock_unassigned_media_pool,
31 file_formats::{
32 MediaLabel,
33 MediaSetLabel,
c4d8542e 34 },
c4d8542e
DM
35};
36
c4d8542e
DM
37/// Media Pool
38pub struct MediaPool {
39
40 name: String,
1057b1f5 41 state_path: PathBuf,
c4d8542e
DM
42
43 media_set_policy: MediaSetPolicy,
44 retention: RetentionPolicy,
cdf39e62
DM
45
46 changer_name: Option<String>,
ab77d660 47 force_media_availability: bool,
cdf39e62 48
30316192
DM
49 // Set this if you do not need to allocate writeable media - this
50 // is useful for list_media()
51 no_media_set_locking: bool,
52
8a0046f5 53 encrypt_fingerprint: Option<Fingerprint>,
c4d8542e
DM
54
55 inventory: Inventory,
c4d8542e
DM
56
57 current_media_set: MediaSet,
7526d864 58 current_media_set_lock: Option<BackupLockGuard>,
c4d8542e
DM
59}
60
61impl MediaPool {
62
63 /// Creates a new instance
cdf39e62
DM
64 ///
65 /// If you specify a `changer_name`, only media accessible via
66 /// that changer is considered available. If you pass `None` for
67 /// `changer`, all offline media is considered available (backups
68 /// to standalone drives may not use media from inside a tape
69 /// library).
c4d8542e
DM
70 pub fn new(
71 name: &str,
72 state_path: &Path,
73 media_set_policy: MediaSetPolicy,
74 retention: RetentionPolicy,
cdf39e62 75 changer_name: Option<String>,
8a0046f5 76 encrypt_fingerprint: Option<Fingerprint>,
30316192 77 no_media_set_locking: bool, // for list_media()
c4d8542e
DM
78 ) -> Result<Self, Error> {
79
30316192
DM
80 let _pool_lock = if no_media_set_locking {
81 None
82 } else {
83 Some(lock_media_pool(state_path, name)?)
84 };
85
c4d8542e
DM
86 let inventory = Inventory::load(state_path)?;
87
88 let current_media_set = match inventory.latest_media_set(name) {
89 Some(set_uuid) => inventory.compute_media_set_members(&set_uuid)?,
90 None => MediaSet::new(),
91 };
92
30316192
DM
93 let current_media_set_lock = if no_media_set_locking {
94 None
95 } else {
96 Some(lock_media_set(state_path, current_media_set.uuid(), None)?)
97 };
98
c4d8542e
DM
99 Ok(MediaPool {
100 name: String::from(name),
1057b1f5 101 state_path: state_path.to_owned(),
c4d8542e
DM
102 media_set_policy,
103 retention,
cdf39e62 104 changer_name,
c4d8542e 105 inventory,
c4d8542e 106 current_media_set,
30316192 107 current_media_set_lock,
8a0046f5 108 encrypt_fingerprint,
ab77d660 109 force_media_availability: false,
30316192 110 no_media_set_locking,
c4d8542e
DM
111 })
112 }
113
ab77d660
DM
114 /// Pretend all Online(x) and Offline media is available
115 ///
116 /// Only media in Vault(y) is considered unavailable.
117 pub fn force_media_availability(&mut self) {
118 self.force_media_availability = true;
119 }
120
32b75d36
DM
121 /// Returns the the current media set
122 pub fn current_media_set(&self) -> &MediaSet {
123 &self.current_media_set
90e16be3
DM
124 }
125
c4d8542e
DM
126 /// Creates a new instance using the media pool configuration
127 pub fn with_config(
c4d8542e
DM
128 state_path: &Path,
129 config: &MediaPoolConfig,
cdf39e62 130 changer_name: Option<String>,
30316192 131 no_media_set_locking: bool, // for list_media()
c4d8542e
DM
132 ) -> Result<Self, Error> {
133
e062ebbc 134 let allocation = config.allocation.clone().unwrap_or_else(|| String::from("continue")).parse()?;
c4d8542e 135
e062ebbc 136 let retention = config.retention.clone().unwrap_or_else(|| String::from("keep")).parse()?;
c4d8542e 137
8a0046f5
DM
138 let encrypt_fingerprint = match config.encrypt {
139 Some(ref fingerprint) => Some(fingerprint.parse()?),
140 None => None,
141 };
142
143 MediaPool::new(
144 &config.name,
145 state_path,
146 allocation,
147 retention,
cdf39e62 148 changer_name,
8a0046f5 149 encrypt_fingerprint,
30316192 150 no_media_set_locking,
8a0046f5 151 )
c4d8542e
DM
152 }
153
154 /// Returns the pool name
155 pub fn name(&self) -> &str {
156 &self.name
157 }
158
d1d74c43 159 /// Returns encryption settings
8a0046f5
DM
160 pub fn encrypt_fingerprint(&self) -> Option<Fingerprint> {
161 self.encrypt_fingerprint.clone()
162 }
163
25350f33
DM
164 pub fn set_media_status_damaged(&mut self, uuid: &Uuid) -> Result<(), Error> {
165 self.inventory.set_media_status_damaged(uuid)
166 }
8a0046f5 167
c4d8542e
DM
168 fn compute_media_state(&self, media_id: &MediaId) -> (MediaStatus, MediaLocation) {
169
cfae8f06 170 let (status, location) = self.inventory.status_and_location(&media_id.label.uuid);
c4d8542e
DM
171
172 match status {
173 MediaStatus::Full | MediaStatus::Damaged | MediaStatus::Retired => {
174 return (status, location);
175 }
176 MediaStatus::Unknown | MediaStatus::Writable => {
177 /* possibly writable - fall through to check */
178 }
179 }
180
181 let set = match media_id.media_set_label {
182 None => return (MediaStatus::Writable, location), // not assigned to any pool
183 Some(ref set) => set,
184 };
185
186 if set.pool != self.name { // should never trigger
187 return (MediaStatus::Unknown, location); // belong to another pool
188 }
189 if set.uuid.as_ref() == [0u8;16] { // not assigned to any pool
190 return (MediaStatus::Writable, location);
191 }
192
193 if &set.uuid != self.current_media_set.uuid() {
194 return (MediaStatus::Full, location); // assume FULL
195 }
196
197 // media is member of current set
198 if self.current_media_set.is_last_media(&media_id.label.uuid) {
199 (MediaStatus::Writable, location) // last set member is writable
200 } else {
201 (MediaStatus::Full, location)
202 }
203 }
204
205 /// Returns the 'MediaId' with associated state
206 pub fn lookup_media(&self, uuid: &Uuid) -> Result<BackupMedia, Error> {
207 let media_id = match self.inventory.lookup_media(uuid) {
208 None => bail!("unable to lookup media {}", uuid),
209 Some(media_id) => media_id.clone(),
210 };
211
212 if let Some(ref set) = media_id.media_set_label {
213 if set.pool != self.name {
214 bail!("media does not belong to pool ({} != {})", set.pool, self.name);
215 }
216 }
217
218 let (status, location) = self.compute_media_state(&media_id);
219
220 Ok(BackupMedia::with_media_id(
221 media_id,
222 location,
223 status,
224 ))
225 }
226
227 /// List all media associated with this pool
228 pub fn list_media(&self) -> Vec<BackupMedia> {
229 let media_id_list = self.inventory.list_pool_media(&self.name);
230
231 media_id_list.into_iter()
232 .map(|media_id| {
233 let (status, location) = self.compute_media_state(&media_id);
234 BackupMedia::with_media_id(
235 media_id,
236 location,
237 status,
238 )
239 })
240 .collect()
241 }
242
243 /// Set media status to FULL.
244 pub fn set_media_status_full(&mut self, uuid: &Uuid) -> Result<(), Error> {
245 let media = self.lookup_media(uuid)?; // check if media belongs to this pool
246 if media.status() != &MediaStatus::Full {
cfae8f06 247 self.inventory.set_media_status_full(uuid)?;
c4d8542e
DM
248 }
249 Ok(())
250 }
251
252 /// Make sure the current media set is usable for writing
253 ///
254 /// If not, starts a new media set. Also creates a new
e953029e 255 /// set if media_set_policy implies it, or if 'force' is true.
ab77d660
DM
256 ///
257 /// Note: We also call this in list_media to compute correct media
258 /// status, so this must not change persistent/saved state.
90e16be3
DM
259 ///
260 /// Returns the reason why we started a new media set (if we do)
30316192
DM
261 pub fn start_write_session(
262 &mut self,
263 current_time: i64,
e953029e 264 force: bool,
30316192
DM
265 ) -> Result<Option<String>, Error> {
266
267 let _pool_lock = if self.no_media_set_locking {
268 None
269 } else {
270 Some(lock_media_pool(&self.state_path, &self.name)?)
271 };
90e16be3 272
30316192
DM
273 self.inventory.reload()?;
274
e953029e
DC
275 let mut create_new_set = if force {
276 Some(String::from("forced"))
277 } else {
278 match self.current_set_usable() {
279 Err(err) => {
280 Some(err.to_string())
281 }
282 Ok(_) => None,
283 }
c4d8542e
DM
284 };
285
90e16be3 286 if create_new_set.is_none() {
c4d8542e
DM
287 match &self.media_set_policy {
288 MediaSetPolicy::AlwaysCreate => {
90e16be3 289 create_new_set = Some(String::from("policy is AlwaysCreate"));
c4d8542e
DM
290 }
291 MediaSetPolicy::CreateAt(event) => {
9a37bd6c 292 if let Some(set_start_time) = self.inventory.media_set_start_time(self.current_media_set.uuid()) {
7549114c 293 if let Ok(Some(alloc_time)) = event.compute_next_event(set_start_time as i64) {
90e16be3
DM
294 if current_time >= alloc_time {
295 create_new_set = Some(String::from("policy CreateAt event triggered"));
c4d8542e
DM
296 }
297 }
298 }
299 }
300 MediaSetPolicy::ContinueCurrent => { /* do nothing here */ }
301 }
302 }
303
90e16be3 304 if create_new_set.is_some() {
c4d8542e 305 let media_set = MediaSet::new();
30316192
DM
306
307 let current_media_set_lock = if self.no_media_set_locking {
308 None
309 } else {
310 Some(lock_media_set(&self.state_path, media_set.uuid(), None)?)
311 };
312
313 self.current_media_set_lock = current_media_set_lock;
c4d8542e
DM
314 self.current_media_set = media_set;
315 }
316
90e16be3 317 Ok(create_new_set)
c4d8542e
DM
318 }
319
320 /// List media in current media set
321 pub fn current_media_list(&self) -> Result<Vec<&Uuid>, Error> {
322 let mut list = Vec::new();
323 for opt_uuid in self.current_media_set.media_list().iter() {
324 match opt_uuid {
325 Some(ref uuid) => list.push(uuid),
326 None => bail!("current_media_list failed - media set is incomplete"),
327 }
328 }
329 Ok(list)
330 }
331
d1d74c43 332 // tests if the media data is considered as expired at specified time
c4d8542e
DM
333 pub fn media_is_expired(&self, media: &BackupMedia, current_time: i64) -> bool {
334 if media.status() != &MediaStatus::Full {
335 return false;
336 }
337
338 let expire_time = self.inventory.media_expire_time(
339 media.id(), &self.media_set_policy, &self.retention);
340
1bed3aed 341 current_time >= expire_time
c4d8542e
DM
342 }
343
8a0046f5
DM
344 // check if a location is considered on site
345 pub fn location_is_available(&self, location: &MediaLocation) -> bool {
346 match location {
cdf39e62 347 MediaLocation::Online(name) => {
ab77d660
DM
348 if self.force_media_availability {
349 true
cdf39e62 350 } else {
ab77d660
DM
351 if let Some(ref changer_name) = self.changer_name {
352 name == changer_name
353 } else {
354 // a standalone drive cannot use media currently inside a library
355 false
356 }
cdf39e62
DM
357 }
358 }
359 MediaLocation::Offline => {
ab77d660
DM
360 if self.force_media_availability {
361 true
362 } else {
363 // consider available for standalone drives
364 self.changer_name.is_none()
365 }
cdf39e62 366 }
8a0046f5
DM
367 MediaLocation::Vault(_) => false,
368 }
369 }
370
371 fn add_media_to_current_set(&mut self, mut media_id: MediaId, current_time: i64) -> Result<(), Error> {
372
30316192
DM
373 if self.current_media_set_lock.is_none() {
374 bail!("add_media_to_current_set: media set is not locked - internal error");
375 }
376
8a0046f5
DM
377 let seq_nr = self.current_media_set.media_list().len() as u64;
378
379 let pool = self.name.clone();
380
381 let encrypt_fingerprint = self.encrypt_fingerprint();
382
383 let set = MediaSetLabel::with_data(
384 &pool,
385 self.current_media_set.uuid().clone(),
386 seq_nr,
387 current_time,
388 encrypt_fingerprint,
389 );
390
391 media_id.media_set_label = Some(set);
392
393 let uuid = media_id.label.uuid.clone();
394
f281b8d3 395 MediaCatalog::overwrite(&self.state_path, &media_id, false)?; // overwite catalog
8a0046f5
DM
396 let clear_media_status = true; // remove Full status
397 self.inventory.store(media_id, clear_media_status)?; // store persistently
398
399 self.current_media_set.add_media(uuid);
400
401 Ok(())
402 }
403
1d3ae833
DM
404 // Get next unassigned media (media not assigned to any pool)
405 pub fn next_unassigned_media(&self, media_list: &[MediaId]) -> Option<MediaId> {
406 let mut free_media = Vec::new();
8a0046f5 407
1d3ae833 408 for media_id in media_list {
c4d8542e 409
9a37bd6c 410 let (status, location) = self.compute_media_state(media_id);
1d3ae833 411 if media_id.media_set_label.is_some() { continue; } // should not happen
c4d8542e 412
1d3ae833
DM
413 if !self.location_is_available(&location) {
414 continue;
415 }
c4d8542e 416
1d3ae833
DM
417 // only consider writable media
418 if status != MediaStatus::Writable { continue; }
c4d8542e 419
1d3ae833
DM
420 free_media.push(media_id);
421 }
c4d8542e 422
1d3ae833
DM
423 // sort free_media, newest first -> oldest last
424 free_media.sort_unstable_by(|a, b| {
425 let mut res = b.label.ctime.cmp(&a.label.ctime);
426 if res == std::cmp::Ordering::Equal {
427 res = b.label.label_text.cmp(&a.label.label_text);
428 }
429 res
430 });
8a0046f5 431
1d3ae833
DM
432 free_media.pop().map(|e| e.clone())
433 }
30316192 434
1d3ae833
DM
435 // Get next empty media
436 pub fn next_empty_media(&self, media_list: &[BackupMedia]) -> Option<MediaId> {
437 let mut empty_media = Vec::new();
30316192 438
1d3ae833
DM
439 for media in media_list.into_iter() {
440 if !self.location_is_available(media.location()) {
441 continue;
c4d8542e 442 }
1d3ae833
DM
443 // already part of a media set?
444 if media.media_set_label().is_none() {
445 // only consider writable empty media
446 if media.status() == &MediaStatus::Writable {
447 empty_media.push(media);
30316192 448 }
1d3ae833
DM
449 }
450 }
30316192 451
1d3ae833
DM
452 // sort empty_media, newest first -> oldest last
453 empty_media.sort_unstable_by(|a, b| {
454 let mut res = b.label().ctime.cmp(&a.label().ctime);
455 if res == std::cmp::Ordering::Equal {
456 res = b.label().label_text.cmp(&a.label().label_text);
af762341 457 }
1d3ae833
DM
458 res
459 });
c4d8542e 460
1d3ae833
DM
461 empty_media.pop().map(|e| e.clone().into_id())
462 }
c4d8542e 463
1d3ae833
DM
464 // Get next expired media
465 pub fn next_expired_media(&self, current_time: i64, media_list: &[BackupMedia]) -> Option<MediaId> {
bb14ed8c 466 let mut expired_media = Vec::new();
c4d8542e 467
1d3ae833
DM
468 for media in media_list.into_iter() {
469 if !self.location_is_available(media.location()) {
470 continue;
471 }
472 // already part of a media set?
1d3ae833
DM
473 if let Some(set) = media.media_set_label() {
474 if &set.uuid == self.current_media_set.uuid() {
c4d8542e
DM
475 continue;
476 }
1d3ae833
DM
477 } else {
478 continue;
479 }
8a0046f5 480
9a37bd6c 481 if !self.media_is_expired(media, current_time) {
1d3ae833 482 continue;
c4d8542e 483 }
c4d8542e 484
1d3ae833
DM
485 expired_media.push(media);
486 }
487
488 // sort expired_media, newest first -> oldest last
489 expired_media.sort_unstable_by(|a, b| {
490 let mut res = b.media_set_label().unwrap().ctime.cmp(&a.media_set_label().unwrap().ctime);
491 if res == std::cmp::Ordering::Equal {
492 res = b.label().label_text.cmp(&a.label().label_text);
493 }
494 res
495 });
30316192 496
1d3ae833
DM
497 if self.no_media_set_locking {
498 expired_media.pop().map(|e| e.clone().into_id())
499 } else {
30316192
DM
500 while let Some(media) = expired_media.pop() {
501 // check if we can modify the media-set (i.e. skip
502 // media used by a restore job)
503 if let Ok(_media_set_lock) = lock_media_set(
504 &self.state_path,
505 &media.media_set_label().unwrap().uuid,
506 Some(std::time::Duration::new(0, 0)), // do not wait
507 ) {
1d3ae833 508 return Some(media.clone().into_id());
30316192 509 }
af762341 510 }
1d3ae833 511 None
25e464c5 512 }
1d3ae833 513 }
c4d8542e 514
1d3ae833
DM
515 /// Guess next writable media
516 ///
517 /// Like alloc_writable_media(), but does not really allocate
518 /// anything (thus it does not need any locks)
519 // Note: Please keep in sync with alloc_writable_media()
520 pub fn guess_next_writable_media(&self, current_time: i64) -> Result<MediaId, Error> {
521 let last_is_writable = self.current_set_usable()?;
c4d8542e 522
1d3ae833
DM
523 if last_is_writable {
524 let last_uuid = self.current_media_set.last_media_uuid().unwrap();
525 let media = self.lookup_media(last_uuid)?;
526 return Ok(media.into_id());
527 }
1057b1f5 528
1d3ae833
DM
529 let media_list = self.list_media();
530 if let Some(media_id) = self.next_empty_media(&media_list) {
531 return Ok(media_id);
532 }
1057b1f5 533
1d3ae833
DM
534 if let Some(media_id) = self.next_expired_media(current_time, &media_list) {
535 return Ok(media_id);
536 }
25e464c5 537
1d3ae833 538 let unassigned_list = self.inventory.list_unassigned_media();
25e464c5 539
1d3ae833
DM
540 if let Some(media_id) = self.next_unassigned_media(&unassigned_list) {
541 return Ok(media_id);
542 }
25e464c5 543
1d3ae833
DM
544 bail!("guess_next_writable_media in pool '{}' failed: no usable media found", self.name());
545 }
25e464c5 546
1d3ae833
DM
547 /// Allocates a writable media to the current media set
548 // Note: Please keep in sync with guess_next_writable_media()
549 pub fn alloc_writable_media(&mut self, current_time: i64) -> Result<Uuid, Error> {
25e464c5 550
1d3ae833
DM
551 if self.current_media_set_lock.is_none() {
552 bail!("alloc_writable_media: media set is not locked - internal error");
25e464c5
DM
553 }
554
1d3ae833
DM
555 let last_is_writable = self.current_set_usable()?;
556
557 if last_is_writable {
558 let last_uuid = self.current_media_set.last_media_uuid().unwrap();
559 let media = self.lookup_media(last_uuid)?;
560 return Ok(media.uuid().clone());
561 }
562
563 { // limit pool lock scope
564 let _pool_lock = lock_media_pool(&self.state_path, &self.name)?;
565
566 self.inventory.reload()?;
567
568 let media_list = self.list_media();
569
570 // try to find empty media in pool, add to media set
571
572 if let Some(media_id) = self.next_empty_media(&media_list) {
573 // found empty media, add to media set an use it
574 println!("found empty media '{}'", media_id.label.label_text);
575 let uuid = media_id.label.uuid.clone();
576 self.add_media_to_current_set(media_id, current_time)?;
577 return Ok(uuid);
af762341 578 }
af762341 579
1d3ae833
DM
580 println!("no empty media in pool, try to reuse expired media");
581
582 if let Some(media_id) = self.next_expired_media(current_time, &media_list) {
583 // found expired media, add to media set an use it
584 println!("reuse expired media '{}'", media_id.label.label_text);
585 let uuid = media_id.label.uuid.clone();
586 self.add_media_to_current_set(media_id, current_time)?;
587 return Ok(uuid);
588 }
589 }
590
591 println!("no empty or expired media in pool, try to find unassigned/free media");
592
593 // try unassigned media
594 let _lock = lock_unassigned_media_pool(&self.state_path)?;
595
596 self.inventory.reload()?;
597
598 let unassigned_list = self.inventory.list_unassigned_media();
599
600 if let Some(media_id) = self.next_unassigned_media(&unassigned_list) {
601 println!("use free/unassigned media '{}'", media_id.label.label_text);
8a0046f5
DM
602 let uuid = media_id.label.uuid.clone();
603 self.add_media_to_current_set(media_id, current_time)?;
604 return Ok(uuid);
c4d8542e 605 }
25e464c5 606
25e464c5 607 bail!("alloc writable media in pool '{}' failed: no usable media found", self.name());
c4d8542e
DM
608 }
609
610 /// check if the current media set is usable for writing
611 ///
612 /// This does several consistency checks, and return if
613 /// the last media in the current set is in writable state.
614 ///
615 /// This return error when the media set must not be used any
616 /// longer because of consistency errors.
617 pub fn current_set_usable(&self) -> Result<bool, Error> {
618
6dd05135
DM
619 let media_list = self.current_media_set.media_list();
620
621 let media_count = media_list.len();
c4d8542e
DM
622 if media_count == 0 {
623 return Ok(false);
624 }
625
626 let set_uuid = self.current_media_set.uuid();
627 let mut last_is_writable = false;
628
6dd05135
DM
629 let mut last_enc: Option<Option<Fingerprint>> = None;
630
631 for (seq, opt_uuid) in media_list.iter().enumerate() {
c4d8542e
DM
632 let uuid = match opt_uuid {
633 None => bail!("media set is incomplete (missing media information)"),
634 Some(uuid) => uuid,
635 };
636 let media = self.lookup_media(uuid)?;
637 match media.media_set_label() {
638 Some(MediaSetLabel { seq_nr, uuid, ..}) if *seq_nr == seq as u64 && uuid == set_uuid => { /* OK */ },
639 Some(MediaSetLabel { seq_nr, uuid, ..}) if uuid == set_uuid => {
640 bail!("media sequence error ({} != {})", *seq_nr, seq);
641 },
642 Some(MediaSetLabel { uuid, ..}) => bail!("media owner error ({} != {}", uuid, set_uuid),
643 None => bail!("media owner error (no owner)"),
644 }
6dd05135
DM
645
646 if let Some(set) = media.media_set_label() { // always true here
647 if set.encryption_key_fingerprint != self.encrypt_fingerprint {
648 bail!("pool encryption key changed");
649 }
650 match last_enc {
651 None => {
652 last_enc = Some(set.encryption_key_fingerprint.clone());
653 }
654 Some(ref last_enc) => {
655 if last_enc != &set.encryption_key_fingerprint {
656 bail!("inconsistent media encryption key");
657 }
658 }
659 }
660 }
661
c4d8542e
DM
662 match media.status() {
663 MediaStatus::Full => { /* OK */ },
664 MediaStatus::Writable if (seq + 1) == media_count => {
b81e37f6
DM
665 let media_location = media.location();
666 if self.location_is_available(media_location) {
667 last_is_writable = true;
668 } else {
669 if let MediaLocation::Vault(vault) = media_location {
c4d8542e
DM
670 bail!("writable media offsite in vault '{}'", vault);
671 }
672 }
673 },
674 _ => bail!("unable to use media set - wrong media status {:?}", media.status()),
675 }
676 }
ab77d660 677
c4d8542e
DM
678 Ok(last_is_writable)
679 }
680
681 /// Generate a human readable name for the media set
682 pub fn generate_media_set_name(
683 &self,
684 media_set_uuid: &Uuid,
685 template: Option<String>,
686 ) -> Result<String, Error> {
687 self.inventory.generate_media_set_name(media_set_uuid, template)
688 }
689
c4d8542e
DM
690}
691
692/// Backup media
693///
694/// Combines 'MediaId' with 'MediaLocation' and 'MediaStatus'
695/// information.
696#[derive(Debug,Serialize,Deserialize,Clone)]
697pub struct BackupMedia {
698 /// Media ID
699 id: MediaId,
700 /// Media location
701 location: MediaLocation,
702 /// Media status
703 status: MediaStatus,
704}
705
706impl BackupMedia {
707
708 /// Creates a new instance
709 pub fn with_media_id(
710 id: MediaId,
711 location: MediaLocation,
712 status: MediaStatus,
713 ) -> Self {
714 Self { id, location, status }
715 }
716
717 /// Returns the media location
718 pub fn location(&self) -> &MediaLocation {
719 &self.location
720 }
721
722 /// Returns the media status
723 pub fn status(&self) -> &MediaStatus {
724 &self.status
725 }
726
727 /// Returns the media uuid
728 pub fn uuid(&self) -> &Uuid {
729 &self.id.label.uuid
730 }
731
732 /// Returns the media set label
6543214d
DM
733 pub fn media_set_label(&self) -> Option<&MediaSetLabel> {
734 self.id.media_set_label.as_ref()
735 }
736
737 /// Returns the media creation time
738 pub fn ctime(&self) -> i64 {
739 self.id.label.ctime
c4d8542e
DM
740 }
741
742 /// Updates the media set label
743 pub fn set_media_set_label(&mut self, set_label: MediaSetLabel) {
744 self.id.media_set_label = Some(set_label);
745 }
54f4ecd4 746
c4d8542e 747 /// Returns the drive label
a78348ac 748 pub fn label(&self) -> &MediaLabel {
c4d8542e
DM
749 &self.id.label
750 }
751
752 /// Returns the media id (drive label + media set label)
753 pub fn id(&self) -> &MediaId {
754 &self.id
755 }
756
8a0046f5
DM
757 /// Returns the media id, consumes self)
758 pub fn into_id(self) -> MediaId {
759 self.id
760 }
761
c4d8542e 762 /// Returns the media label (Barcode)
8446fbca
DM
763 pub fn label_text(&self) -> &str {
764 &self.id.label.label_text
c4d8542e
DM
765 }
766}