use std::{clone::Clone, collections::HashMap, path::Path};
use serde_json::Value;
use devicemapper::DmNameBuf;
use crate::{
engine::{
engine::KeyActions,
shared::{create_pool_idempotent_or_err, validate_name, validate_paths},
strat_engine::{
cmd::verify_binaries,
dm::get_dm,
keys::{MemoryFilesystem, StratKeyActions},
liminal::{find_all, LiminalDevices},
pool::StratPool,
},
structures::Table,
types::{
CreateAction, DeleteAction, DevUuid, EncryptionInfo, LockedPoolInfo, RenameAction,
ReportType, SetUnlockAction, UdevEngineEvent, UnlockMethod,
},
Engine, Name, Pool, PoolUuid, Report,
},
stratis::{ErrorEnum, StratisError, StratisResult},
};
#[derive(Debug)]
pub struct StratEngine {
pools: Table<PoolUuid, StratPool>,
liminal_devices: LiminalDevices,
watched_dev_last_event_nrs: HashMap<PoolUuid, HashMap<DmNameBuf, u32>>,
key_handler: StratKeyActions,
key_fs: MemoryFilesystem,
}
impl StratEngine {
pub fn initialize() -> StratisResult<StratEngine> {
verify_binaries()?;
let mut liminal_devices = LiminalDevices::default();
let mut pools = Table::default();
for (pool_name, pool_uuid, pool) in liminal_devices.setup_pools(find_all()?) {
pools.insert(pool_name, pool_uuid, pool);
}
Ok(StratEngine {
pools,
liminal_devices,
watched_dev_last_event_nrs: HashMap::new(),
key_handler: StratKeyActions,
key_fs: MemoryFilesystem::new()?,
})
}
#[cfg(test)]
pub fn teardown(self) -> StratisResult<()> {
let mut untorndown_pools = Vec::new();
for (_, uuid, mut pool) in self.pools {
pool.teardown()
.unwrap_or_else(|_| untorndown_pools.push(uuid));
}
if untorndown_pools.is_empty() {
Ok(())
} else {
let err_msg = format!(
"Failed to teardown already set up pools: {:?}",
untorndown_pools
);
Err(StratisError::Engine(ErrorEnum::Error, err_msg))
}
}
}
impl<'a> Into<Value> for &'a StratEngine {
fn into(self) -> Value {
let json = json!({
"pools": Value::Array(
self.pools.iter()
.map(|(name, uuid, pool)| {
let mut json = json!({
"uuid": Value::from(uuid.to_string()),
"name": Value::from(name.to_string()),
});
if let Value::Object(ref mut map) = json {
map.extend(
if let Value::Object(map) = <&StratPool as Into<Value>>::into(pool) {
map.into_iter()
} else {
unreachable!("StratPool conversion returns a JSON object");
}
);
} else {
unreachable!("json!() always creates a JSON object")
}
json
})
.collect()
),
});
if let (Value::Object(mut j), Value::Object(map)) = (
json,
<&LiminalDevices as Into<Value>>::into(&self.liminal_devices),
) {
j.extend(map.into_iter());
Value::Object(j)
} else {
unreachable!("json!() and LiminalDevices::into() always return JSON object");
}
}
}
impl Report for StratEngine {
fn engine_state_report(&self) -> Value {
self.into()
}
fn get_report(&self, report_type: ReportType) -> Value {
match report_type {
ReportType::ErroredPoolDevices => (&self.liminal_devices).into(),
}
}
}
impl Engine for StratEngine {
fn handle_event(&mut self, event: &UdevEngineEvent) -> Option<(Name, PoolUuid, &dyn Pool)> {
if let Some((pool_uuid, pool_name, pool)) =
self.liminal_devices.block_evaluate(&self.pools, event)
{
self.pools.insert(pool_name.clone(), pool_uuid, pool);
Some((
pool_name,
pool_uuid,
self.pools.get_by_uuid(pool_uuid).expect("just_inserted").1 as &dyn Pool,
))
} else {
None
}
}
fn create_pool(
&mut self,
name: &str,
blockdev_paths: &[&Path],
redundancy: Option<u16>,
encryption_info: &EncryptionInfo,
) -> StratisResult<CreateAction<PoolUuid>> {
let redundancy = calculate_redundancy!(redundancy);
validate_name(name)?;
validate_paths(blockdev_paths)?;
match self.pools.get_by_name(name) {
Some((_, pool)) => create_pool_idempotent_or_err(pool, name, blockdev_paths),
None => {
if blockdev_paths.is_empty() {
Err(StratisError::Engine(
ErrorEnum::Invalid,
"At least one blockdev is required to create a pool.".to_string(),
))
} else {
let (uuid, pool) =
StratPool::initialize(name, blockdev_paths, redundancy, encryption_info)?;
let name = Name::new(name.to_owned());
self.pools.insert(name, uuid, pool);
Ok(CreateAction::Created(uuid))
}
}
}
}
fn destroy_pool(&mut self, uuid: PoolUuid) -> StratisResult<DeleteAction<PoolUuid>> {
if let Some((_, pool)) = self.pools.get_by_uuid(uuid) {
if pool.has_filesystems() {
return Err(StratisError::Engine(
ErrorEnum::Busy,
"filesystems remaining on pool".into(),
));
};
} else {
return Ok(DeleteAction::Identity);
}
let (pool_name, mut pool) = self
.pools
.remove_by_uuid(uuid)
.expect("Must succeed since self.pools.get_by_uuid() returned a value");
if let Err(err) = pool.destroy() {
self.pools.insert(pool_name, uuid, pool);
Err(err)
} else {
Ok(DeleteAction::Deleted(uuid))
}
}
fn rename_pool(
&mut self,
uuid: PoolUuid,
new_name: &str,
) -> StratisResult<RenameAction<PoolUuid>> {
validate_name(new_name)?;
let old_name = rename_pool_pre_idem!(self; uuid; new_name);
let (_, mut pool) = self
.pools
.remove_by_uuid(uuid)
.expect("Must succeed since self.pools.get_by_uuid() returned a value");
let new_name = Name::new(new_name.to_owned());
if let Err(err) = pool.write_metadata(&new_name) {
self.pools.insert(old_name, uuid, pool);
Err(err)
} else {
self.pools.insert(new_name, uuid, pool);
let (new_name, pool) = self.pools.get_by_uuid(uuid).expect("Inserted above");
pool.udev_pool_change(&new_name);
Ok(RenameAction::Renamed(uuid))
}
}
fn unlock_pool(
&mut self,
pool_uuid: PoolUuid,
unlock_method: UnlockMethod,
) -> StratisResult<SetUnlockAction<DevUuid>> {
let unlocked = self
.liminal_devices
.unlock_pool(&self.pools, pool_uuid, unlock_method)?;
Ok(SetUnlockAction::new(unlocked))
}
fn get_pool(&self, uuid: PoolUuid) -> Option<(Name, &dyn Pool)> {
get_pool!(self; uuid)
}
fn get_mut_pool(&mut self, uuid: PoolUuid) -> Option<(Name, &mut dyn Pool)> {
get_mut_pool!(self; uuid)
}
fn locked_pools(&self) -> HashMap<PoolUuid, LockedPoolInfo> {
self.liminal_devices.locked_pools()
}
fn pools(&self) -> Vec<(Name, PoolUuid, &dyn Pool)> {
self.pools
.iter()
.map(|(name, uuid, pool)| (name.clone(), *uuid, pool as &dyn Pool))
.collect()
}
fn pools_mut(&mut self) -> Vec<(Name, PoolUuid, &mut dyn Pool)> {
self.pools
.iter_mut()
.map(|(name, uuid, pool)| (name.clone(), *uuid, pool as &mut dyn Pool))
.collect()
}
fn evented(&mut self) -> StratisResult<()> {
let device_list: HashMap<_, _> = get_dm()
.list_devices()?
.into_iter()
.map(|(dm_name, _, event_nr)| {
(
dm_name,
event_nr.expect("Supported DM versions always provide a value"),
)
})
.collect();
for (pool_name, pool_uuid, pool) in self.pools.iter_mut() {
let dev_names = pool.get_eventing_dev_names(*pool_uuid);
let event_nrs = device_list
.iter()
.filter_map(|(dm_name, event_nr)| {
if dev_names.contains(dm_name) {
Some((dm_name.clone(), *event_nr))
} else {
None
}
})
.collect::<HashMap<_, _>>();
if self.watched_dev_last_event_nrs.get(pool_uuid) != Some(&event_nrs) {
pool.event_on(*pool_uuid, pool_name)?;
}
self.watched_dev_last_event_nrs
.insert(*pool_uuid, event_nrs);
}
Ok(())
}
fn get_key_handler(&self) -> &dyn KeyActions {
&self.key_handler as &dyn KeyActions
}
fn get_key_handler_mut(&mut self) -> &mut dyn KeyActions {
&mut self.key_handler as &mut dyn KeyActions
}
fn is_sim(&self) -> bool {
false
}
}
#[cfg(test)]
mod test {
use devicemapper::Sectors;
use crate::engine::{
strat_engine::{
cmd,
tests::{loopbacked, real},
},
types::EngineAction,
};
use super::*;
fn test_pool_rename(paths: &[&Path]) {
let mut engine = StratEngine::initialize().unwrap();
let name1 = "name1";
let uuid1 = engine
.create_pool(name1, paths, None, &EncryptionInfo::default())
.unwrap()
.changed()
.unwrap();
let fs_name1 = "testfs1";
let fs_name2 = "testfs2";
let (_, pool) = engine.pools.get_mut_by_uuid(uuid1).unwrap();
let fs_uuid1 = pool
.create_filesystems(name1, uuid1, &[(fs_name1, None)])
.unwrap()
.changed()
.unwrap();
let fs_uuid2 = pool
.create_filesystems(name1, uuid1, &[(fs_name2, None)])
.unwrap()
.changed()
.unwrap();
cmd::udev_settle().unwrap();
assert!(Path::new(&format!("/dev/stratis/{}/{}", name1, fs_name1)).exists());
assert!(Path::new(&format!("/dev/stratis/{}/{}", name1, fs_name2)).exists());
let name2 = "name2";
let action = engine.rename_pool(uuid1, name2).unwrap();
cmd::udev_settle().unwrap();
assert!(!Path::new(&format!("/dev/stratis/{}/{}", name1, fs_name1)).exists());
assert!(!Path::new(&format!("/dev/stratis/{}/{}", name1, fs_name2)).exists());
assert!(Path::new(&format!("/dev/stratis/{}/{}", name2, fs_name1)).exists());
assert!(Path::new(&format!("/dev/stratis/{}/{}", name2, fs_name2)).exists());
let (_, pool) = engine.pools.get_mut_by_uuid(uuid1).unwrap();
pool.destroy_filesystems(
name2,
fs_uuid1
.into_iter()
.map(|(_, u)| u)
.collect::<Vec<_>>()
.as_slice(),
)
.unwrap();
pool.destroy_filesystems(
name2,
fs_uuid2
.into_iter()
.map(|(_, u)| u)
.collect::<Vec<_>>()
.as_slice(),
)
.unwrap();
assert_eq!(action, RenameAction::Renamed(uuid1));
engine.teardown().unwrap();
let engine = StratEngine::initialize().unwrap();
let pool_name: String = engine.get_pool(uuid1).unwrap().0.to_owned();
assert_eq!(pool_name, name2);
}
#[test]
fn loop_test_pool_rename() {
loopbacked::test_with_spec(
&loopbacked::DeviceLimits::Range(1, 3, Some(Sectors(10 * 1024 * 1024))),
test_pool_rename,
);
}
#[test]
fn real_test_pool_rename() {
real::test_with_spec(
&real::DeviceLimits::AtLeast(1, Some(Sectors(10 * 1024 * 1024)), None),
test_pool_rename,
);
}
fn test_setup(paths: &[&Path]) {
assert!(paths.len() > 1);
let (paths1, paths2) = paths.split_at(paths.len() / 2);
let mut engine = StratEngine::initialize().unwrap();
let name1 = "name1";
let uuid1 = engine
.create_pool(name1, paths1, None, &EncryptionInfo::default())
.unwrap()
.changed()
.unwrap();
let name2 = "name2";
let uuid2 = engine
.create_pool(name2, paths2, None, &EncryptionInfo::default())
.unwrap()
.changed()
.unwrap();
assert!(engine.get_pool(uuid1).is_some());
assert!(engine.get_pool(uuid2).is_some());
engine.teardown().unwrap();
let engine = StratEngine::initialize().unwrap();
assert!(engine.get_pool(uuid1).is_some());
assert!(engine.get_pool(uuid2).is_some());
engine.teardown().unwrap();
let engine = StratEngine::initialize().unwrap();
assert!(engine.get_pool(uuid1).is_some());
assert!(engine.get_pool(uuid2).is_some());
engine.teardown().unwrap();
}
#[test]
fn loop_test_setup() {
loopbacked::test_with_spec(&loopbacked::DeviceLimits::Range(2, 3, None), test_setup);
}
#[test]
fn real_test_setup() {
real::test_with_spec(&real::DeviceLimits::AtLeast(2, None, None), test_setup);
}
}