use std::{
collections::{HashMap, HashSet},
fmt,
};
use serde_json::Value;
use crate::{
engine::{
engine::Pool,
strat_engine::{
backstore::CryptActivationHandle,
liminal::{
device_info::{DeviceBag, DeviceSet, LInfo, LLuksInfo, LStratisInfo},
identify::{identify_block_device, DeviceInfo, LuksInfo, StratisInfo},
setup::{get_bdas, get_blockdevs, get_metadata},
},
metadata::StratisIdentifiers,
pool::StratPool,
},
structures::Table,
types::{DevUuid, LockedPoolInfo, Name, PoolUuid, UdevEngineEvent, UnlockMethod},
},
stratis::{ErrorEnum, StratisError, StratisResult},
};
#[derive(Debug)]
enum Destination {
Hopeless(String),
Errored(String),
}
impl fmt::Display for Destination {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Destination::Hopeless(val) => write!(f, "{}", val),
Destination::Errored(val) => write!(f, "{}", val),
}
}
}
#[derive(Debug, Default, Eq, PartialEq)]
pub struct LiminalDevices {
errored_pool_devices: HashMap<PoolUuid, DeviceSet>,
hopeless_device_sets: HashMap<PoolUuid, DeviceBag>,
}
impl LiminalDevices {
#[allow(dead_code)]
fn invariant(&self) {
assert!(self
.errored_pool_devices
.keys()
.cloned()
.collect::<HashSet<PoolUuid>>()
.intersection(
&self
.hopeless_device_sets
.keys()
.cloned()
.collect::<HashSet<PoolUuid>>()
)
.next()
.is_none());
}
pub fn unlock_pool(
&mut self,
pools: &Table<PoolUuid, StratPool>,
pool_uuid: PoolUuid,
unlock_method: UnlockMethod,
) -> StratisResult<Vec<DevUuid>> {
fn handle_luks(luks_info: &LLuksInfo, unlock_method: UnlockMethod) -> StratisResult<()> {
if CryptActivationHandle::setup(&luks_info.ids.devnode, unlock_method)?.is_some() {
Ok(())
} else {
Err(StratisError::Engine(
ErrorEnum::Invalid,
format!(
"Block device {} does not appear to be formatted with
the proper Stratis LUKS2 metadata.",
luks_info.ids.devnode.display(),
),
))
}
}
let unlocked = match self.errored_pool_devices.get(&pool_uuid) {
Some(map) => {
if map.all_unencrypted() {
return Err(StratisError::Engine(
ErrorEnum::Error,
format!(
"Attempted to unlock set of devices belonging to an unencrypted pool with UUID {}",
pool_uuid,
),
));
}
let mut unlocked = Vec::new();
for (dev_uuid, info) in map.iter() {
match info {
LInfo::Stratis(_) => (),
LInfo::Luks(ref luks_info) => match handle_luks(luks_info, unlock_method) {
Ok(()) => unlocked.push(*dev_uuid),
Err(e) => return Err(e),
},
}
}
unlocked
}
None => match pools.get_by_uuid(pool_uuid) {
Some((_, pool)) => {
if pool.is_encrypted() {
vec![]
} else {
return Err(StratisError::Engine(
ErrorEnum::Error,
format!(
"Pool with UUID {} is not encrypted and cannot be unlocked.",
pool_uuid,
),
));
}
}
None => {
return Err(StratisError::Engine(
ErrorEnum::Error,
format!(
"No devices with UUID {} have been registered with stratisd.",
pool_uuid,
),
))
}
},
};
Ok(unlocked)
}
pub fn locked_pools(&self) -> HashMap<PoolUuid, LockedPoolInfo> {
self.errored_pool_devices
.iter()
.filter_map(|(pool_uuid, map)| map.locked_pool_info().map(|info| (*pool_uuid, info)))
.collect()
}
pub fn setup_pools(
&mut self,
all_devices: (
HashMap<PoolUuid, Vec<LuksInfo>>,
HashMap<PoolUuid, Vec<StratisInfo>>,
),
) -> Vec<(Name, PoolUuid, StratPool)> {
let table = Table::default();
let (mut luks_devices, mut stratis_devices) = all_devices;
let pool_uuids: HashSet<PoolUuid> = luks_devices
.keys()
.cloned()
.collect::<HashSet<PoolUuid>>()
.union(&stratis_devices.keys().cloned().collect())
.cloned()
.collect();
pool_uuids
.iter()
.filter_map(|pool_uuid| {
let luks_infos = luks_devices.remove(pool_uuid);
let stratis_infos = stratis_devices.remove(pool_uuid);
let mut infos: Vec<DeviceInfo> = stratis_infos
.unwrap_or_else(Vec::new)
.drain(..)
.map(DeviceInfo::Stratis)
.chain(
luks_infos
.unwrap_or_else(Vec::new)
.drain(..)
.map(DeviceInfo::Luks),
)
.collect();
let mut info_map = DeviceSet::new();
while !infos.is_empty() && !self.hopeless_device_sets.contains_key(pool_uuid) {
let info: DeviceInfo = infos.pop().expect("!infos.is_empty()");
if let Err(mut hopeless) = info_map.process_info_add(info) {
hopeless.extend(infos.drain(..).map(|x| x.into()));
self.hopeless_device_sets.insert(*pool_uuid, hopeless);
}
}
if !self.hopeless_device_sets.contains_key(pool_uuid) {
self.try_setup_pool(&table, *pool_uuid, info_map)
.map(|(pool_name, pool)| (pool_name, *pool_uuid, pool))
} else {
None
}
})
.collect::<Vec<(Name, PoolUuid, StratPool)>>()
}
fn try_setup_pool(
&mut self,
pools: &Table<PoolUuid, StratPool>,
pool_uuid: PoolUuid,
infos: DeviceSet,
) -> Option<(Name, StratPool)> {
assert!(pools.get_by_uuid(pool_uuid).is_none());
assert!(self.errored_pool_devices.get(&pool_uuid).is_none());
assert!(self.hopeless_device_sets.get(&pool_uuid).is_none());
fn setup_pool(
pools: &Table<PoolUuid, StratPool>,
pool_uuid: PoolUuid,
infos: &HashMap<DevUuid, &LStratisInfo>,
) -> Result<(Name, StratPool), Destination> {
let bdas = match get_bdas(infos) {
Err(err) => Err(
Destination::Errored(format!(
"There was an error encountered when reading the BDAs for the devices found for pool with UUID {}: {}",
pool_uuid,
err))),
Ok(infos) => Ok(infos),
}?;
if let Some((dev_uuid, bda)) = bdas.iter().find(|(dev_uuid, bda)| {
**dev_uuid != bda.dev_uuid() || pool_uuid != bda.pool_uuid()
}) {
return Err(
Destination::Hopeless(format!(
"Mismatch between Stratis identifiers previously read and those found on some BDA: {} != {}",
StratisIdentifiers::new(pool_uuid, *dev_uuid),
StratisIdentifiers::new(bda.pool_uuid(), bda.dev_uuid())
)));
}
let (timestamp, metadata) = match get_metadata(infos, &bdas) {
Err(err) => return Err(
Destination::Errored(format!(
"There was an error encountered when reading the metadata for the devices found for pool with UUID {}: {}",
pool_uuid,
err))),
Ok(None) => return Err(
Destination::Errored(format!(
"No metadata found on devices associated with pool UUID {}",
pool_uuid))),
Ok(Some((timestamp, metadata))) => (timestamp, metadata),
};
if let Some((uuid, _)) = pools.get_by_name(&metadata.name) {
return Err(
Destination::Errored(format!(
"There is a pool name conflict. The devices currently being processed have been identified as belonging to the pool with UUID {} and name {}, but a pool with the same name and UUID {} is already active",
pool_uuid,
&metadata.name,
uuid)));
}
let (datadevs, cachedevs) = match get_blockdevs(&metadata.backstore, infos, bdas) {
Err(err) => return Err(
Destination::Errored(format!(
"There was an error encountered when calculating the block devices for pool with UUID {} and name {}: {}",
pool_uuid,
&metadata.name,
err))),
Ok((datadevs, cachedevs)) => (datadevs, cachedevs),
};
if datadevs.get(0).is_none() {
return Err(Destination::Hopeless(format!(
"There do not appear to be any data devices in the set with pool UUID {}",
pool_uuid
)));
}
let num_with_luks = datadevs
.iter()
.filter(|sbd| sbd.encryption_info().is_encrypted())
.count();
if num_with_luks != 0 && num_with_luks != datadevs.len() {
return Err(
Destination::Errored(format!(
"Some data devices in the set belonging to pool with UUID {} and name {} appear to be encrypted devices managed by Stratis, and some do not",
pool_uuid,
&metadata.name)));
}
StratPool::setup(pool_uuid, datadevs, cachedevs, timestamp, &metadata).map_err(|err| {
Destination::Errored(format!(
"An attempt to set up pool with UUID {} from the assembled devices failed: {}",
pool_uuid, err
))
})
}
let opened = match infos.as_opened_set() {
Some(opened) => opened,
None => {
self.errored_pool_devices.insert(pool_uuid, infos);
return None;
}
};
let result = setup_pool(pools, pool_uuid, &opened);
match result {
Ok((pool_name, pool)) => {
info!(
"Pool with name \"{}\" and UUID \"{}\" set up",
pool_name, pool_uuid
);
Some((pool_name, pool))
}
Err(Destination::Hopeless(err)) => {
warn!(
"Attempt to set up pool failed, moving to hopeless devices: {}",
err
);
self.hopeless_device_sets
.insert(pool_uuid, infos.into_bag());
None
}
Err(Destination::Errored(err)) => {
info!("Attempt to set up pool failed, but it may be possible to set up the pool later, if the situation changes: {}", err);
self.errored_pool_devices.insert(pool_uuid, infos);
None
}
}
}
pub fn block_evaluate(
&mut self,
pools: &Table<PoolUuid, StratPool>,
event: &UdevEngineEvent,
) -> Option<(PoolUuid, Name, StratPool)> {
let event_type = event.event_type();
if event_type == libudev::EventType::Add || event_type == libudev::EventType::Change {
identify_block_device(event).and_then(move |info| {
let stratis_identifiers = info.stratis_identifiers();
let pool_uuid = stratis_identifiers.pool_uuid;
let device_uuid = stratis_identifiers.device_uuid;
if let Some((_, pool)) = pools.get_by_uuid(pool_uuid) {
if pool.get_strat_blockdev(device_uuid).is_none() {
warn!("Found a device with {} that identifies itself as belonging to pool with UUID {}, but that pool is already up and running and does not appear to contain the device",
info,
pool_uuid);
}
None
} else if let Some(mut set) = self.hopeless_device_sets.remove(&pool_uuid) {
set.insert(info.into());
self.hopeless_device_sets.insert(pool_uuid, set);
None
} else {
let mut devices = self
.errored_pool_devices
.remove(&pool_uuid)
.unwrap_or_else(DeviceSet::new);
if let Err(hopeless) = devices.process_info_add(info) {
self.hopeless_device_sets.insert(pool_uuid, hopeless);
return None;
}
self.try_setup_pool(pools, pool_uuid, devices)
.map(|(name, pool)| (pool_uuid, name, pool))
}
})
} else if event_type == libudev::EventType::Remove {
identify_block_device(event).and_then(move |info| {
let stratis_identifiers = info.stratis_identifiers();
let pool_uuid = stratis_identifiers.pool_uuid;
let device_uuid = stratis_identifiers.device_uuid;
if let Some((_, pool)) = pools.get_by_uuid(pool_uuid) {
if pool.get_strat_blockdev(device_uuid).is_some() {
warn!("udev reports that a device with {} that appears to belong to a pool with UUID {} has just been removed; this is likely to result in data loss",
info,
pool_uuid);
}
None
} else if let Some(mut set) = self.hopeless_device_sets.remove(&pool_uuid) {
set.remove(&info.into());
self.hopeless_device_sets.insert(pool_uuid, set);
None
} else {
let mut devices = self
.errored_pool_devices
.remove(&pool_uuid)
.unwrap_or_else(DeviceSet::new);
devices.process_info_remove(info);
self.try_setup_pool(pools, pool_uuid, devices)
.map(|(name, pool)| (pool_uuid, name, pool))
}
})
} else {
None
}
}
}
impl<'a> Into<Value> for &'a LiminalDevices {
fn into(self) -> Value {
json!({
"errored_pools": Value::Array(
self.errored_pool_devices
.iter()
.map(|(uuid, map)| {
json!({
"pool_uuid": uuid.to_string(),
"devices": <&DeviceSet as Into<Value>>::into(&map),
})
})
.collect(),
),
"hopeless_devices": Value::Array(
self.hopeless_device_sets
.iter()
.map(|(uuid, set)| {
json!({
"pool_uuid": uuid.to_string(),
"devices": <&DeviceBag as Into<Value>>::into(&set),
})
})
.collect()
)
})
}
}