use std::io::{Read, Seek};
use chrono::{DateTime, Utc};
use crate::{
engine::{
strat_engine::{
metadata::{
mda,
sizes::{BDAExtendedSize, BlockdevSize, MDADataSize, STATIC_HEADER_SIZE},
static_header::{MetadataLocation, StaticHeader, StratisIdentifiers},
},
writing::SyncAll,
},
types::{DevUuid, PoolUuid},
},
stratis::StratisResult,
};
#[derive(Debug)]
pub struct BDA {
header: StaticHeader,
regions: mda::MDARegions,
}
impl Default for BDA {
fn default() -> BDA {
BDA::new(
StratisIdentifiers::new(PoolUuid::nil(), DevUuid::nil()),
MDADataSize::default(),
BlockdevSize::default(),
0,
)
}
}
impl BDA {
pub fn new(
identifiers: StratisIdentifiers,
mda_data_size: MDADataSize,
blkdev_size: BlockdevSize,
initialization_time: u64,
) -> BDA {
let header =
StaticHeader::new(identifiers, mda_data_size, blkdev_size, initialization_time);
let regions = mda::MDARegions::new(header.mda_size);
BDA { header, regions }
}
pub fn initialize<F>(&self, f: &mut F) -> StratisResult<()>
where
F: Seek + SyncAll,
{
self.header.write(f, MetadataLocation::Both)?;
self.regions
.initialize(STATIC_HEADER_SIZE.sectors().bytes(), f)?;
Ok(())
}
pub fn load<F>(header: StaticHeader, f: &mut F) -> StratisResult<Option<BDA>>
where
F: Read + Seek + SyncAll,
{
let regions =
mda::MDARegions::load(STATIC_HEADER_SIZE.sectors().bytes(), header.mda_size, f)?;
Ok(Some(BDA { header, regions }))
}
pub fn save_state<F>(
&mut self,
time: &DateTime<Utc>,
metadata: &[u8],
f: &mut F,
) -> StratisResult<()>
where
F: Seek + SyncAll,
{
self.regions
.save_state(STATIC_HEADER_SIZE.sectors().bytes(), time, metadata, f)
}
pub fn load_state<F>(&self, mut f: &mut F) -> StratisResult<Option<Vec<u8>>>
where
F: Read + Seek,
{
self.regions
.load_state(STATIC_HEADER_SIZE.sectors().bytes(), &mut f)
}
pub fn last_update_time(&self) -> Option<&DateTime<Utc>> {
self.regions.last_update_time()
}
pub fn dev_uuid(&self) -> DevUuid {
self.header.identifiers.device_uuid
}
pub fn pool_uuid(&self) -> PoolUuid {
self.header.identifiers.pool_uuid
}
pub fn dev_size(&self) -> BlockdevSize {
self.header.blkdev_size
}
pub fn extended_size(&self) -> BDAExtendedSize {
self.header.bda_extended_size()
}
pub fn max_data_size(&self) -> MDADataSize {
self.regions.max_data_size()
}
pub fn initialization_time(&self) -> u64 {
self.header.initialization_time
}
}
#[cfg(test)]
mod tests {
use std::{io::Cursor, thread, time};
use proptest::{collection::vec, num};
use crate::engine::strat_engine::metadata::static_header::tests::{
random_static_header, static_header_strategy,
};
use super::*;
proptest! {
#[test]
fn empty_bda(ref sh in static_header_strategy()) {
let buf_size = convert_test!(*sh.mda_size.bda_size().sectors().bytes(), u128, usize);
let mut buf = Cursor::new(vec![0; buf_size]);
let bda = BDA::new(
sh.identifiers,
sh.mda_size.region_size().data_size(),
sh.blkdev_size,
Utc::now().timestamp() as u64,
);
bda.initialize(&mut buf).unwrap();
let read_results = StaticHeader::read_sigblocks(&mut buf);
let header = StaticHeader::repair_sigblocks(&mut buf, read_results, StaticHeader::do_nothing).unwrap().unwrap();
let bda = BDA::load(header, &mut buf).unwrap().unwrap();
prop_assert!(bda.last_update_time().is_none());
}
}
#[test]
fn test_early_times_err() {
let data = [0u8; 3];
let sleep_time = time::Duration::from_secs(1);
let sh = random_static_header(0, 0);
let mut buf = Cursor::new(vec![
0;
convert_test!(
*sh.blkdev_size.sectors().bytes(),
u128,
usize
)
]);
let mut bda = BDA::new(
sh.identifiers,
sh.mda_size.region_size().data_size(),
sh.blkdev_size,
Utc::now().timestamp() as u64,
);
bda.initialize(&mut buf).unwrap();
let timestamp0 = Utc::now();
thread::sleep(sleep_time);
let timestamp1 = Utc::now();
let mut buf = Cursor::new(vec![
0;
convert_test!(
*sh.blkdev_size.sectors().bytes(),
u128,
usize
)
]);
bda.save_state(×tamp1, &data, &mut buf).unwrap();
assert_matches!(bda.save_state(×tamp0, &data, &mut buf), Err(_));
let timestamp2 = Utc::now();
thread::sleep(sleep_time);
let timestamp3 = Utc::now();
bda.save_state(×tamp3, &data, &mut buf).unwrap();
assert_matches!(bda.save_state(×tamp2, &data, &mut buf), Err(_));
}
proptest! {
#[test]
fn check_state(
ref sh in static_header_strategy(),
ref state in vec(num::u8::ANY, 1..100),
ref next_state in vec(num::u8::ANY, 1..100)
) {
let buf_size = convert_test!(*sh.mda_size.bda_size().sectors().bytes(), u128, usize);
let mut buf = Cursor::new(vec![0; buf_size]);
let mut bda = BDA::new(
sh.identifiers,
sh.mda_size.region_size().data_size(),
sh.blkdev_size,
Utc::now().timestamp() as u64,
);
bda.initialize(&mut buf).unwrap();
let current_time = Utc::now();
bda.save_state(¤t_time, state, &mut buf).unwrap();
let loaded_state = bda.load_state(&mut buf).unwrap();
prop_assert!(bda.last_update_time().map(|t| t == ¤t_time).unwrap_or(false));
prop_assert!(loaded_state.map(|s| &s == state).unwrap_or(false));
let read_results = StaticHeader::read_sigblocks(&mut buf);
let header = StaticHeader::repair_sigblocks(&mut buf, read_results, StaticHeader::write_header).unwrap().unwrap();
let mut bda = BDA::load(header, &mut buf).unwrap().unwrap();
let loaded_state = bda.load_state(&mut buf).unwrap();
prop_assert!(loaded_state.map(|s| &s == state).unwrap_or(false));
prop_assert!(bda.last_update_time().map(|t| t == ¤t_time).unwrap_or(false));
let current_time = Utc::now();
bda.save_state(¤t_time, next_state, &mut buf)
.unwrap();
let loaded_state = bda.load_state(&mut buf).unwrap();
prop_assert!(loaded_state.map(|s| &s == next_state).unwrap_or(false));
prop_assert!(bda.last_update_time().map(|t| t == ¤t_time).unwrap_or(false));
}
}
}