2020-12-10 10:41:35 +00:00
|
|
|
//! Media Pool
|
|
|
|
//!
|
|
|
|
//! A set of backup medias.
|
|
|
|
//!
|
|
|
|
//! This struct manages backup media state during backup. The main
|
2021-03-10 15:37:09 +00:00
|
|
|
//! purpose is to allocate media sets and assign new tapes to it.
|
2020-12-10 10:41:35 +00:00
|
|
|
//!
|
|
|
|
//!
|
|
|
|
|
2021-03-09 09:00:26 +00:00
|
|
|
use std::path::{PathBuf, Path};
|
2021-03-22 05:32:18 +00:00
|
|
|
use std::fs::File;
|
|
|
|
|
2020-12-10 10:41:35 +00:00
|
|
|
use anyhow::{bail, Error};
|
|
|
|
use ::serde::{Deserialize, Serialize};
|
|
|
|
|
|
|
|
use proxmox::tools::Uuid;
|
|
|
|
|
|
|
|
use crate::{
|
2021-01-18 12:36:11 +00:00
|
|
|
backup::Fingerprint,
|
2020-12-10 10:41:35 +00:00
|
|
|
api2::types::{
|
|
|
|
MediaStatus,
|
2020-12-16 09:45:58 +00:00
|
|
|
MediaLocation,
|
2020-12-10 10:41:35 +00:00
|
|
|
MediaSetPolicy,
|
|
|
|
RetentionPolicy,
|
|
|
|
MediaPoolConfig,
|
|
|
|
},
|
|
|
|
tools::systemd::time::compute_next_event,
|
|
|
|
tape::{
|
|
|
|
MediaId,
|
|
|
|
MediaSet,
|
|
|
|
Inventory,
|
2021-04-13 07:43:00 +00:00
|
|
|
MediaCatalog,
|
2021-03-22 05:32:18 +00:00
|
|
|
lock_media_set,
|
|
|
|
lock_media_pool,
|
|
|
|
lock_unassigned_media_pool,
|
2020-12-10 10:41:35 +00:00
|
|
|
file_formats::{
|
2020-12-14 16:37:16 +00:00
|
|
|
MediaLabel,
|
2020-12-10 10:41:35 +00:00
|
|
|
MediaSetLabel,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/// Media Pool
|
|
|
|
pub struct MediaPool {
|
|
|
|
|
|
|
|
name: String,
|
2021-03-09 09:00:26 +00:00
|
|
|
state_path: PathBuf,
|
2020-12-10 10:41:35 +00:00
|
|
|
|
|
|
|
media_set_policy: MediaSetPolicy,
|
|
|
|
retention: RetentionPolicy,
|
2021-02-04 09:15:18 +00:00
|
|
|
|
|
|
|
changer_name: Option<String>,
|
2021-03-04 07:52:58 +00:00
|
|
|
force_media_availability: bool,
|
2021-02-04 09:15:18 +00:00
|
|
|
|
2021-03-22 05:32:18 +00:00
|
|
|
// Set this if you do not need to allocate writeable media - this
|
|
|
|
// is useful for list_media()
|
|
|
|
no_media_set_locking: bool,
|
|
|
|
|
2021-01-18 12:36:11 +00:00
|
|
|
encrypt_fingerprint: Option<Fingerprint>,
|
2020-12-10 10:41:35 +00:00
|
|
|
|
|
|
|
inventory: Inventory,
|
|
|
|
|
|
|
|
current_media_set: MediaSet,
|
2021-03-22 05:32:18 +00:00
|
|
|
current_media_set_lock: Option<File>,
|
2020-12-10 10:41:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl MediaPool {
|
|
|
|
|
|
|
|
/// Creates a new instance
|
2021-02-04 09:15:18 +00:00
|
|
|
///
|
|
|
|
/// If you specify a `changer_name`, only media accessible via
|
|
|
|
/// that changer is considered available. If you pass `None` for
|
|
|
|
/// `changer`, all offline media is considered available (backups
|
|
|
|
/// to standalone drives may not use media from inside a tape
|
|
|
|
/// library).
|
2020-12-10 10:41:35 +00:00
|
|
|
pub fn new(
|
|
|
|
name: &str,
|
|
|
|
state_path: &Path,
|
|
|
|
media_set_policy: MediaSetPolicy,
|
|
|
|
retention: RetentionPolicy,
|
2021-02-04 09:15:18 +00:00
|
|
|
changer_name: Option<String>,
|
2021-01-18 12:36:11 +00:00
|
|
|
encrypt_fingerprint: Option<Fingerprint>,
|
2021-03-22 05:32:18 +00:00
|
|
|
no_media_set_locking: bool, // for list_media()
|
2020-12-10 10:41:35 +00:00
|
|
|
) -> Result<Self, Error> {
|
|
|
|
|
2021-03-22 05:32:18 +00:00
|
|
|
let _pool_lock = if no_media_set_locking {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(lock_media_pool(state_path, name)?)
|
|
|
|
};
|
|
|
|
|
2020-12-10 10:41:35 +00:00
|
|
|
let inventory = Inventory::load(state_path)?;
|
|
|
|
|
|
|
|
let current_media_set = match inventory.latest_media_set(name) {
|
|
|
|
Some(set_uuid) => inventory.compute_media_set_members(&set_uuid)?,
|
|
|
|
None => MediaSet::new(),
|
|
|
|
};
|
|
|
|
|
2021-03-22 05:32:18 +00:00
|
|
|
let current_media_set_lock = if no_media_set_locking {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(lock_media_set(state_path, current_media_set.uuid(), None)?)
|
|
|
|
};
|
|
|
|
|
2020-12-10 10:41:35 +00:00
|
|
|
Ok(MediaPool {
|
|
|
|
name: String::from(name),
|
2021-03-09 09:00:26 +00:00
|
|
|
state_path: state_path.to_owned(),
|
2020-12-10 10:41:35 +00:00
|
|
|
media_set_policy,
|
|
|
|
retention,
|
2021-02-04 09:15:18 +00:00
|
|
|
changer_name,
|
2020-12-10 10:41:35 +00:00
|
|
|
inventory,
|
|
|
|
current_media_set,
|
2021-03-22 05:32:18 +00:00
|
|
|
current_media_set_lock,
|
2021-01-18 12:36:11 +00:00
|
|
|
encrypt_fingerprint,
|
2021-03-04 07:52:58 +00:00
|
|
|
force_media_availability: false,
|
2021-03-22 05:32:18 +00:00
|
|
|
no_media_set_locking,
|
2020-12-10 10:41:35 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-03-04 07:52:58 +00:00
|
|
|
/// Pretend all Online(x) and Offline media is available
|
|
|
|
///
|
|
|
|
/// Only media in Vault(y) is considered unavailable.
|
|
|
|
pub fn force_media_availability(&mut self) {
|
|
|
|
self.force_media_availability = true;
|
|
|
|
}
|
|
|
|
|
2021-03-19 05:58:02 +00:00
|
|
|
/// Returns the the current media set
|
|
|
|
pub fn current_media_set(&self) -> &MediaSet {
|
|
|
|
&self.current_media_set
|
2021-03-05 08:58:36 +00:00
|
|
|
}
|
|
|
|
|
2020-12-10 10:41:35 +00:00
|
|
|
/// Creates a new instance using the media pool configuration
|
|
|
|
pub fn with_config(
|
|
|
|
state_path: &Path,
|
|
|
|
config: &MediaPoolConfig,
|
2021-02-04 09:15:18 +00:00
|
|
|
changer_name: Option<String>,
|
2021-03-22 05:32:18 +00:00
|
|
|
no_media_set_locking: bool, // for list_media()
|
2020-12-10 10:41:35 +00:00
|
|
|
) -> Result<Self, Error> {
|
|
|
|
|
2021-01-19 13:04:46 +00:00
|
|
|
let allocation = config.allocation.clone().unwrap_or_else(|| String::from("continue")).parse()?;
|
2020-12-10 10:41:35 +00:00
|
|
|
|
2021-01-19 13:04:46 +00:00
|
|
|
let retention = config.retention.clone().unwrap_or_else(|| String::from("keep")).parse()?;
|
2020-12-10 10:41:35 +00:00
|
|
|
|
2021-01-18 12:36:11 +00:00
|
|
|
let encrypt_fingerprint = match config.encrypt {
|
|
|
|
Some(ref fingerprint) => Some(fingerprint.parse()?),
|
|
|
|
None => None,
|
|
|
|
};
|
|
|
|
|
|
|
|
MediaPool::new(
|
|
|
|
&config.name,
|
|
|
|
state_path,
|
|
|
|
allocation,
|
|
|
|
retention,
|
2021-02-04 09:15:18 +00:00
|
|
|
changer_name,
|
2021-01-18 12:36:11 +00:00
|
|
|
encrypt_fingerprint,
|
2021-03-22 05:32:18 +00:00
|
|
|
no_media_set_locking,
|
2021-01-18 12:36:11 +00:00
|
|
|
)
|
2020-12-10 10:41:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the pool name
|
|
|
|
pub fn name(&self) -> &str {
|
|
|
|
&self.name
|
|
|
|
}
|
|
|
|
|
2021-03-10 15:37:09 +00:00
|
|
|
/// Returns encryption settings
|
2021-01-18 12:36:11 +00:00
|
|
|
pub fn encrypt_fingerprint(&self) -> Option<Fingerprint> {
|
|
|
|
self.encrypt_fingerprint.clone()
|
|
|
|
}
|
|
|
|
|
2021-02-26 08:00:50 +00:00
|
|
|
pub fn set_media_status_damaged(&mut self, uuid: &Uuid) -> Result<(), Error> {
|
|
|
|
self.inventory.set_media_status_damaged(uuid)
|
|
|
|
}
|
2021-01-18 12:36:11 +00:00
|
|
|
|
2020-12-10 10:41:35 +00:00
|
|
|
fn compute_media_state(&self, media_id: &MediaId) -> (MediaStatus, MediaLocation) {
|
|
|
|
|
2021-01-01 15:15:13 +00:00
|
|
|
let (status, location) = self.inventory.status_and_location(&media_id.label.uuid);
|
2020-12-10 10:41:35 +00:00
|
|
|
|
|
|
|
match status {
|
|
|
|
MediaStatus::Full | MediaStatus::Damaged | MediaStatus::Retired => {
|
|
|
|
return (status, location);
|
|
|
|
}
|
|
|
|
MediaStatus::Unknown | MediaStatus::Writable => {
|
|
|
|
/* possibly writable - fall through to check */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let set = match media_id.media_set_label {
|
|
|
|
None => return (MediaStatus::Writable, location), // not assigned to any pool
|
|
|
|
Some(ref set) => set,
|
|
|
|
};
|
|
|
|
|
|
|
|
if set.pool != self.name { // should never trigger
|
|
|
|
return (MediaStatus::Unknown, location); // belong to another pool
|
|
|
|
}
|
|
|
|
if set.uuid.as_ref() == [0u8;16] { // not assigned to any pool
|
|
|
|
return (MediaStatus::Writable, location);
|
|
|
|
}
|
|
|
|
|
|
|
|
if &set.uuid != self.current_media_set.uuid() {
|
|
|
|
return (MediaStatus::Full, location); // assume FULL
|
|
|
|
}
|
|
|
|
|
|
|
|
// media is member of current set
|
|
|
|
if self.current_media_set.is_last_media(&media_id.label.uuid) {
|
|
|
|
(MediaStatus::Writable, location) // last set member is writable
|
|
|
|
} else {
|
|
|
|
(MediaStatus::Full, location)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the 'MediaId' with associated state
|
|
|
|
pub fn lookup_media(&self, uuid: &Uuid) -> Result<BackupMedia, Error> {
|
|
|
|
let media_id = match self.inventory.lookup_media(uuid) {
|
|
|
|
None => bail!("unable to lookup media {}", uuid),
|
|
|
|
Some(media_id) => media_id.clone(),
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(ref set) = media_id.media_set_label {
|
|
|
|
if set.pool != self.name {
|
|
|
|
bail!("media does not belong to pool ({} != {})", set.pool, self.name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let (status, location) = self.compute_media_state(&media_id);
|
|
|
|
|
|
|
|
Ok(BackupMedia::with_media_id(
|
|
|
|
media_id,
|
|
|
|
location,
|
|
|
|
status,
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// List all media associated with this pool
|
|
|
|
pub fn list_media(&self) -> Vec<BackupMedia> {
|
|
|
|
let media_id_list = self.inventory.list_pool_media(&self.name);
|
|
|
|
|
|
|
|
media_id_list.into_iter()
|
|
|
|
.map(|media_id| {
|
|
|
|
let (status, location) = self.compute_media_state(&media_id);
|
|
|
|
BackupMedia::with_media_id(
|
|
|
|
media_id,
|
|
|
|
location,
|
|
|
|
status,
|
|
|
|
)
|
|
|
|
})
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Set media status to FULL.
|
|
|
|
pub fn set_media_status_full(&mut self, uuid: &Uuid) -> Result<(), Error> {
|
|
|
|
let media = self.lookup_media(uuid)?; // check if media belongs to this pool
|
|
|
|
if media.status() != &MediaStatus::Full {
|
2021-01-01 15:15:13 +00:00
|
|
|
self.inventory.set_media_status_full(uuid)?;
|
2020-12-10 10:41:35 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Make sure the current media set is usable for writing
|
|
|
|
///
|
|
|
|
/// If not, starts a new media set. Also creates a new
|
|
|
|
/// set if media_set_policy implies it.
|
2021-03-04 07:52:58 +00:00
|
|
|
///
|
|
|
|
/// Note: We also call this in list_media to compute correct media
|
|
|
|
/// status, so this must not change persistent/saved state.
|
2021-03-05 08:58:36 +00:00
|
|
|
///
|
|
|
|
/// Returns the reason why we started a new media set (if we do)
|
2021-03-22 05:32:18 +00:00
|
|
|
pub fn start_write_session(
|
|
|
|
&mut self,
|
|
|
|
current_time: i64,
|
|
|
|
) -> Result<Option<String>, Error> {
|
|
|
|
|
|
|
|
let _pool_lock = if self.no_media_set_locking {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(lock_media_pool(&self.state_path, &self.name)?)
|
|
|
|
};
|
2021-03-05 08:58:36 +00:00
|
|
|
|
2021-03-22 05:32:18 +00:00
|
|
|
self.inventory.reload()?;
|
|
|
|
|
|
|
|
let mut create_new_set = match self.current_set_usable() {
|
2021-03-05 08:58:36 +00:00
|
|
|
Err(err) => {
|
|
|
|
Some(err.to_string())
|
|
|
|
}
|
|
|
|
Ok(_) => None,
|
2020-12-10 10:41:35 +00:00
|
|
|
};
|
|
|
|
|
2021-03-05 08:58:36 +00:00
|
|
|
if create_new_set.is_none() {
|
2020-12-10 10:41:35 +00:00
|
|
|
match &self.media_set_policy {
|
|
|
|
MediaSetPolicy::AlwaysCreate => {
|
2021-03-05 08:58:36 +00:00
|
|
|
create_new_set = Some(String::from("policy is AlwaysCreate"));
|
2020-12-10 10:41:35 +00:00
|
|
|
}
|
|
|
|
MediaSetPolicy::CreateAt(event) => {
|
|
|
|
if let Some(set_start_time) = self.inventory.media_set_start_time(&self.current_media_set.uuid()) {
|
|
|
|
if let Ok(Some(alloc_time)) = compute_next_event(event, set_start_time as i64, false) {
|
2021-03-05 08:58:36 +00:00
|
|
|
if current_time >= alloc_time {
|
|
|
|
create_new_set = Some(String::from("policy CreateAt event triggered"));
|
2020-12-10 10:41:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
MediaSetPolicy::ContinueCurrent => { /* do nothing here */ }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-05 08:58:36 +00:00
|
|
|
if create_new_set.is_some() {
|
2020-12-10 10:41:35 +00:00
|
|
|
let media_set = MediaSet::new();
|
2021-03-22 05:32:18 +00:00
|
|
|
|
|
|
|
let current_media_set_lock = if self.no_media_set_locking {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(lock_media_set(&self.state_path, media_set.uuid(), None)?)
|
|
|
|
};
|
|
|
|
|
|
|
|
self.current_media_set_lock = current_media_set_lock;
|
2020-12-10 10:41:35 +00:00
|
|
|
self.current_media_set = media_set;
|
|
|
|
}
|
|
|
|
|
2021-03-05 08:58:36 +00:00
|
|
|
Ok(create_new_set)
|
2020-12-10 10:41:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// List media in current media set
|
|
|
|
pub fn current_media_list(&self) -> Result<Vec<&Uuid>, Error> {
|
|
|
|
let mut list = Vec::new();
|
|
|
|
for opt_uuid in self.current_media_set.media_list().iter() {
|
|
|
|
match opt_uuid {
|
|
|
|
Some(ref uuid) => list.push(uuid),
|
|
|
|
None => bail!("current_media_list failed - media set is incomplete"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(list)
|
|
|
|
}
|
|
|
|
|
2021-03-10 15:37:09 +00:00
|
|
|
// tests if the media data is considered as expired at specified time
|
2020-12-10 10:41:35 +00:00
|
|
|
pub fn media_is_expired(&self, media: &BackupMedia, current_time: i64) -> bool {
|
|
|
|
if media.status() != &MediaStatus::Full {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
let expire_time = self.inventory.media_expire_time(
|
|
|
|
media.id(), &self.media_set_policy, &self.retention);
|
|
|
|
|
2021-03-04 11:36:45 +00:00
|
|
|
current_time >= expire_time
|
2020-12-10 10:41:35 +00:00
|
|
|
}
|
|
|
|
|
2021-01-18 12:36:11 +00:00
|
|
|
// check if a location is considered on site
|
|
|
|
pub fn location_is_available(&self, location: &MediaLocation) -> bool {
|
|
|
|
match location {
|
2021-02-04 09:15:18 +00:00
|
|
|
MediaLocation::Online(name) => {
|
2021-03-04 07:52:58 +00:00
|
|
|
if self.force_media_availability {
|
|
|
|
true
|
2021-02-04 09:15:18 +00:00
|
|
|
} else {
|
2021-03-04 07:52:58 +00:00
|
|
|
if let Some(ref changer_name) = self.changer_name {
|
|
|
|
name == changer_name
|
|
|
|
} else {
|
|
|
|
// a standalone drive cannot use media currently inside a library
|
|
|
|
false
|
|
|
|
}
|
2021-02-04 09:15:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
MediaLocation::Offline => {
|
2021-03-04 07:52:58 +00:00
|
|
|
if self.force_media_availability {
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
// consider available for standalone drives
|
|
|
|
self.changer_name.is_none()
|
|
|
|
}
|
2021-02-04 09:15:18 +00:00
|
|
|
}
|
2021-01-18 12:36:11 +00:00
|
|
|
MediaLocation::Vault(_) => false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn add_media_to_current_set(&mut self, mut media_id: MediaId, current_time: i64) -> Result<(), Error> {
|
|
|
|
|
2021-03-22 05:32:18 +00:00
|
|
|
if self.current_media_set_lock.is_none() {
|
|
|
|
bail!("add_media_to_current_set: media set is not locked - internal error");
|
|
|
|
}
|
|
|
|
|
2021-01-18 12:36:11 +00:00
|
|
|
let seq_nr = self.current_media_set.media_list().len() as u64;
|
|
|
|
|
|
|
|
let pool = self.name.clone();
|
|
|
|
|
|
|
|
let encrypt_fingerprint = self.encrypt_fingerprint();
|
|
|
|
|
|
|
|
let set = MediaSetLabel::with_data(
|
|
|
|
&pool,
|
|
|
|
self.current_media_set.uuid().clone(),
|
|
|
|
seq_nr,
|
|
|
|
current_time,
|
|
|
|
encrypt_fingerprint,
|
|
|
|
);
|
|
|
|
|
|
|
|
media_id.media_set_label = Some(set);
|
|
|
|
|
|
|
|
let uuid = media_id.label.uuid.clone();
|
|
|
|
|
2021-04-13 07:43:00 +00:00
|
|
|
MediaCatalog::overwrite(&self.state_path, &media_id, false)?; // overwite catalog
|
2021-01-18 12:36:11 +00:00
|
|
|
let clear_media_status = true; // remove Full status
|
|
|
|
self.inventory.store(media_id, clear_media_status)?; // store persistently
|
|
|
|
|
|
|
|
self.current_media_set.add_media(uuid);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-12-10 10:41:35 +00:00
|
|
|
/// Allocates a writable media to the current media set
|
|
|
|
pub fn alloc_writable_media(&mut self, current_time: i64) -> Result<Uuid, Error> {
|
|
|
|
|
2021-03-22 05:32:18 +00:00
|
|
|
if self.current_media_set_lock.is_none() {
|
|
|
|
bail!("alloc_writable_media: media set is not locked - internal error");
|
|
|
|
}
|
|
|
|
|
2020-12-10 10:41:35 +00:00
|
|
|
let last_is_writable = self.current_set_usable()?;
|
|
|
|
|
|
|
|
if last_is_writable {
|
|
|
|
let last_uuid = self.current_media_set.last_media_uuid().unwrap();
|
|
|
|
let media = self.lookup_media(last_uuid)?;
|
|
|
|
return Ok(media.uuid().clone());
|
|
|
|
}
|
|
|
|
|
|
|
|
// try to find empty media in pool, add to media set
|
|
|
|
|
2021-03-22 05:32:18 +00:00
|
|
|
{ // limit pool lock scope
|
|
|
|
let _pool_lock = lock_media_pool(&self.state_path, &self.name)?;
|
2020-12-10 10:41:35 +00:00
|
|
|
|
2021-03-22 05:32:18 +00:00
|
|
|
self.inventory.reload()?;
|
2021-01-18 12:36:11 +00:00
|
|
|
|
2021-03-22 05:32:18 +00:00
|
|
|
let media_list = self.list_media();
|
|
|
|
|
|
|
|
let mut empty_media = Vec::new();
|
|
|
|
let mut used_media = Vec::new();
|
|
|
|
|
|
|
|
for media in media_list.into_iter() {
|
|
|
|
if !self.location_is_available(media.location()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// already part of a media set?
|
|
|
|
if media.media_set_label().is_some() {
|
|
|
|
used_media.push(media);
|
|
|
|
} else {
|
|
|
|
// only consider writable empty media
|
|
|
|
if media.status() == &MediaStatus::Writable {
|
|
|
|
empty_media.push(media);
|
|
|
|
}
|
2021-01-18 12:36:11 +00:00
|
|
|
}
|
2020-12-10 10:41:35 +00:00
|
|
|
}
|
|
|
|
|
2021-03-22 05:32:18 +00:00
|
|
|
// sort empty_media, newest first -> oldest last
|
|
|
|
empty_media.sort_unstable_by(|a, b| {
|
|
|
|
let mut res = b.label().ctime.cmp(&a.label().ctime);
|
|
|
|
if res == std::cmp::Ordering::Equal {
|
|
|
|
res = b.label().label_text.cmp(&a.label().label_text);
|
|
|
|
}
|
|
|
|
res
|
|
|
|
});
|
|
|
|
|
|
|
|
if let Some(media) = empty_media.pop() {
|
|
|
|
// found empty media, add to media set an use it
|
|
|
|
let uuid = media.uuid().clone();
|
|
|
|
self.add_media_to_current_set(media.into_id(), current_time)?;
|
|
|
|
return Ok(uuid);
|
2021-03-09 07:33:21 +00:00
|
|
|
}
|
2020-12-10 10:41:35 +00:00
|
|
|
|
2021-03-22 05:32:18 +00:00
|
|
|
println!("no empty media in pool, try to reuse expired media");
|
2020-12-10 10:41:35 +00:00
|
|
|
|
2021-03-22 05:32:18 +00:00
|
|
|
let mut expired_media = Vec::new();
|
2020-12-10 10:41:35 +00:00
|
|
|
|
2021-03-22 05:32:18 +00:00
|
|
|
for media in used_media.into_iter() {
|
|
|
|
if let Some(set) = media.media_set_label() {
|
|
|
|
if &set.uuid == self.current_media_set.uuid() {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
} else {
|
2020-12-10 10:41:35 +00:00
|
|
|
continue;
|
|
|
|
}
|
2021-01-18 12:36:11 +00:00
|
|
|
|
2021-03-22 05:32:18 +00:00
|
|
|
if self.media_is_expired(&media, current_time) {
|
|
|
|
println!("found expired media on media '{}'", media.label_text());
|
|
|
|
expired_media.push(media);
|
|
|
|
}
|
2020-12-10 10:41:35 +00:00
|
|
|
}
|
|
|
|
|
2021-03-22 05:32:18 +00:00
|
|
|
// sort expired_media, newest first -> oldest last
|
|
|
|
expired_media.sort_unstable_by(|a, b| {
|
|
|
|
let mut res = b.media_set_label().unwrap().ctime.cmp(&a.media_set_label().unwrap().ctime);
|
|
|
|
if res == std::cmp::Ordering::Equal {
|
|
|
|
res = b.label().label_text.cmp(&a.label().label_text);
|
|
|
|
}
|
|
|
|
res
|
|
|
|
});
|
|
|
|
|
|
|
|
while let Some(media) = expired_media.pop() {
|
|
|
|
// check if we can modify the media-set (i.e. skip
|
|
|
|
// media used by a restore job)
|
|
|
|
if let Ok(_media_set_lock) = lock_media_set(
|
|
|
|
&self.state_path,
|
|
|
|
&media.media_set_label().unwrap().uuid,
|
|
|
|
Some(std::time::Duration::new(0, 0)), // do not wait
|
|
|
|
) {
|
|
|
|
println!("reuse expired media '{}'", media.label_text());
|
|
|
|
let uuid = media.uuid().clone();
|
|
|
|
self.add_media_to_current_set(media.into_id(), current_time)?;
|
|
|
|
return Ok(uuid);
|
|
|
|
}
|
2021-03-09 07:33:21 +00:00
|
|
|
}
|
2021-01-13 13:25:51 +00:00
|
|
|
}
|
2020-12-10 10:41:35 +00:00
|
|
|
|
2021-01-13 13:25:51 +00:00
|
|
|
println!("no expired media in pool, try to find unassigned/free media");
|
2020-12-10 10:41:35 +00:00
|
|
|
|
2021-01-13 13:25:51 +00:00
|
|
|
// try unassigned media
|
2021-03-22 05:32:18 +00:00
|
|
|
let _lock = lock_unassigned_media_pool(&self.state_path)?;
|
2021-03-09 09:00:26 +00:00
|
|
|
|
|
|
|
self.inventory.reload()?;
|
|
|
|
|
2021-01-13 13:25:51 +00:00
|
|
|
let mut free_media = Vec::new();
|
|
|
|
|
|
|
|
for media_id in self.inventory.list_unassigned_media() {
|
|
|
|
|
|
|
|
let (status, location) = self.compute_media_state(&media_id);
|
|
|
|
if media_id.media_set_label.is_some() { continue; } // should not happen
|
|
|
|
|
2021-01-18 12:36:11 +00:00
|
|
|
if !self.location_is_available(&location) {
|
|
|
|
continue;
|
2020-12-10 10:41:35 +00:00
|
|
|
}
|
2021-01-13 13:25:51 +00:00
|
|
|
|
|
|
|
// only consider writable media
|
|
|
|
if status != MediaStatus::Writable { continue; }
|
|
|
|
|
|
|
|
free_media.push(media_id);
|
|
|
|
}
|
|
|
|
|
2021-03-09 07:33:21 +00:00
|
|
|
// sort free_media, newest first -> oldest last
|
|
|
|
free_media.sort_unstable_by(|a, b| {
|
|
|
|
let mut res = b.label.ctime.cmp(&a.label.ctime);
|
|
|
|
if res == std::cmp::Ordering::Equal {
|
|
|
|
res = b.label.label_text.cmp(&a.label.label_text);
|
|
|
|
}
|
|
|
|
res
|
|
|
|
});
|
|
|
|
|
2021-01-18 12:36:11 +00:00
|
|
|
if let Some(media_id) = free_media.pop() {
|
|
|
|
println!("use free media '{}'", media_id.label.label_text);
|
|
|
|
let uuid = media_id.label.uuid.clone();
|
|
|
|
self.add_media_to_current_set(media_id, current_time)?;
|
|
|
|
return Ok(uuid);
|
2020-12-10 10:41:35 +00:00
|
|
|
}
|
2021-01-13 13:25:51 +00:00
|
|
|
|
|
|
|
bail!("alloc writable media in pool '{}' failed: no usable media found", self.name());
|
2020-12-10 10:41:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// check if the current media set is usable for writing
|
|
|
|
///
|
|
|
|
/// This does several consistency checks, and return if
|
|
|
|
/// the last media in the current set is in writable state.
|
|
|
|
///
|
|
|
|
/// This return error when the media set must not be used any
|
|
|
|
/// longer because of consistency errors.
|
|
|
|
pub fn current_set_usable(&self) -> Result<bool, Error> {
|
|
|
|
|
2021-01-20 10:24:58 +00:00
|
|
|
let media_list = self.current_media_set.media_list();
|
|
|
|
|
|
|
|
let media_count = media_list.len();
|
2020-12-10 10:41:35 +00:00
|
|
|
if media_count == 0 {
|
|
|
|
return Ok(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
let set_uuid = self.current_media_set.uuid();
|
|
|
|
let mut last_is_writable = false;
|
|
|
|
|
2021-01-20 10:24:58 +00:00
|
|
|
let mut last_enc: Option<Option<Fingerprint>> = None;
|
|
|
|
|
|
|
|
for (seq, opt_uuid) in media_list.iter().enumerate() {
|
2020-12-10 10:41:35 +00:00
|
|
|
let uuid = match opt_uuid {
|
|
|
|
None => bail!("media set is incomplete (missing media information)"),
|
|
|
|
Some(uuid) => uuid,
|
|
|
|
};
|
|
|
|
let media = self.lookup_media(uuid)?;
|
|
|
|
match media.media_set_label() {
|
|
|
|
Some(MediaSetLabel { seq_nr, uuid, ..}) if *seq_nr == seq as u64 && uuid == set_uuid => { /* OK */ },
|
|
|
|
Some(MediaSetLabel { seq_nr, uuid, ..}) if uuid == set_uuid => {
|
|
|
|
bail!("media sequence error ({} != {})", *seq_nr, seq);
|
|
|
|
},
|
|
|
|
Some(MediaSetLabel { uuid, ..}) => bail!("media owner error ({} != {}", uuid, set_uuid),
|
|
|
|
None => bail!("media owner error (no owner)"),
|
|
|
|
}
|
2021-01-20 10:24:58 +00:00
|
|
|
|
|
|
|
if let Some(set) = media.media_set_label() { // always true here
|
|
|
|
if set.encryption_key_fingerprint != self.encrypt_fingerprint {
|
|
|
|
bail!("pool encryption key changed");
|
|
|
|
}
|
|
|
|
match last_enc {
|
|
|
|
None => {
|
|
|
|
last_enc = Some(set.encryption_key_fingerprint.clone());
|
|
|
|
}
|
|
|
|
Some(ref last_enc) => {
|
|
|
|
if last_enc != &set.encryption_key_fingerprint {
|
|
|
|
bail!("inconsistent media encryption key");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-10 10:41:35 +00:00
|
|
|
match media.status() {
|
|
|
|
MediaStatus::Full => { /* OK */ },
|
|
|
|
MediaStatus::Writable if (seq + 1) == media_count => {
|
2021-02-04 08:39:16 +00:00
|
|
|
let media_location = media.location();
|
|
|
|
if self.location_is_available(media_location) {
|
|
|
|
last_is_writable = true;
|
|
|
|
} else {
|
|
|
|
if let MediaLocation::Vault(vault) = media_location {
|
2020-12-10 10:41:35 +00:00
|
|
|
bail!("writable media offsite in vault '{}'", vault);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_ => bail!("unable to use media set - wrong media status {:?}", media.status()),
|
|
|
|
}
|
|
|
|
}
|
2021-03-04 07:52:58 +00:00
|
|
|
|
2020-12-10 10:41:35 +00:00
|
|
|
Ok(last_is_writable)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Generate a human readable name for the media set
|
|
|
|
pub fn generate_media_set_name(
|
|
|
|
&self,
|
|
|
|
media_set_uuid: &Uuid,
|
|
|
|
template: Option<String>,
|
|
|
|
) -> Result<String, Error> {
|
|
|
|
self.inventory.generate_media_set_name(media_set_uuid, template)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Backup media
|
|
|
|
///
|
|
|
|
/// Combines 'MediaId' with 'MediaLocation' and 'MediaStatus'
|
|
|
|
/// information.
|
|
|
|
#[derive(Debug,Serialize,Deserialize,Clone)]
|
|
|
|
pub struct BackupMedia {
|
|
|
|
/// Media ID
|
|
|
|
id: MediaId,
|
|
|
|
/// Media location
|
|
|
|
location: MediaLocation,
|
|
|
|
/// Media status
|
|
|
|
status: MediaStatus,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl BackupMedia {
|
|
|
|
|
|
|
|
/// Creates a new instance
|
|
|
|
pub fn with_media_id(
|
|
|
|
id: MediaId,
|
|
|
|
location: MediaLocation,
|
|
|
|
status: MediaStatus,
|
|
|
|
) -> Self {
|
|
|
|
Self { id, location, status }
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the media location
|
|
|
|
pub fn location(&self) -> &MediaLocation {
|
|
|
|
&self.location
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the media status
|
|
|
|
pub fn status(&self) -> &MediaStatus {
|
|
|
|
&self.status
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the media uuid
|
|
|
|
pub fn uuid(&self) -> &Uuid {
|
|
|
|
&self.id.label.uuid
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the media set label
|
2021-01-12 11:00:39 +00:00
|
|
|
pub fn media_set_label(&self) -> Option<&MediaSetLabel> {
|
|
|
|
self.id.media_set_label.as_ref()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the media creation time
|
|
|
|
pub fn ctime(&self) -> i64 {
|
|
|
|
self.id.label.ctime
|
2020-12-10 10:41:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Updates the media set label
|
|
|
|
pub fn set_media_set_label(&mut self, set_label: MediaSetLabel) {
|
|
|
|
self.id.media_set_label = Some(set_label);
|
|
|
|
}
|
2021-01-01 09:03:59 +00:00
|
|
|
|
2020-12-10 10:41:35 +00:00
|
|
|
/// Returns the drive label
|
2020-12-14 16:37:16 +00:00
|
|
|
pub fn label(&self) -> &MediaLabel {
|
2020-12-10 10:41:35 +00:00
|
|
|
&self.id.label
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the media id (drive label + media set label)
|
|
|
|
pub fn id(&self) -> &MediaId {
|
|
|
|
&self.id
|
|
|
|
}
|
|
|
|
|
2021-01-18 12:36:11 +00:00
|
|
|
/// Returns the media id, consumes self)
|
|
|
|
pub fn into_id(self) -> MediaId {
|
|
|
|
self.id
|
|
|
|
}
|
|
|
|
|
2020-12-10 10:41:35 +00:00
|
|
|
/// Returns the media label (Barcode)
|
2021-01-13 12:26:59 +00:00
|
|
|
pub fn label_text(&self) -> &str {
|
|
|
|
&self.id.label.label_text
|
2020-12-10 10:41:35 +00:00
|
|
|
}
|
|
|
|
}
|