use std::{
collections::HashMap,
fmt::{self},
path::Path,
str::FromStr,
sync::{Arc, RwLock},
};
use rpki::ca::idexchange::MyHandle;
use crate::commons::eventsourcing::{locks::HandleLocks, KeyStoreKey, KeyValueError, KeyValueStore, Storable};
pub trait WalSupport: Storable {
type Command: WalCommand;
type Change: WalChange;
type Error: std::error::Error + From<WalStoreError>;
fn revision(&self) -> u64;
fn apply(&mut self, set: WalSet<Self>);
fn process_command(&self, command: Self::Command) -> Result<Vec<Self::Change>, Self::Error>;
}
pub trait WalCommand: fmt::Display {
fn handle(&self) -> &MyHandle;
}
pub trait WalChange: fmt::Display + Eq + PartialEq + Send + Sync + Storable {}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct WalSet<T: WalSupport> {
revision: u64,
summary: String,
changes: Vec<T::Change>,
}
impl<T: WalSupport> WalSet<T> {
pub fn into_changes(self) -> Vec<T::Change> {
self.changes
}
}
#[derive(Debug)]
pub struct WalStore<T: WalSupport> {
kv: KeyValueStore,
cache: RwLock<HashMap<MyHandle, Arc<T>>>,
locks: HandleLocks,
}
impl<T: WalSupport> WalStore<T> {
pub fn disk(krill_data_dir: &Path, name_space: &str) -> WalStoreResult<Self> {
let mut path = krill_data_dir.to_path_buf();
path.push(name_space);
let kv = KeyValueStore::disk(krill_data_dir, name_space)?;
let cache = RwLock::new(HashMap::new());
let locks = HandleLocks::default();
Ok(WalStore { kv, cache, locks })
}
pub fn warm(&self) -> WalStoreResult<()> {
for handle in self.list()? {
let latest = self
.get_latest(&handle)
.map_err(|e| WalStoreError::WarmupFailed(handle.clone(), e.to_string()))?;
self.cache.write().unwrap().insert(handle, latest);
}
Ok(())
}
pub fn add(&self, handle: &MyHandle, instance: T) -> WalStoreResult<()> {
let handle_lock = self.locks.for_handle(handle.clone());
let _write = handle_lock.write();
let instance = Arc::new(instance);
let key = Self::key_for_snapshot(handle);
self.kv.store_new(&key, &instance)?; self.cache.write().unwrap().insert(handle.clone(), instance);
Ok(())
}
pub fn has(&self, handle: &MyHandle) -> WalStoreResult<bool> {
let key = Self::key_for_snapshot(handle);
self.kv.has(&key).map_err(WalStoreError::KeyStoreError)
}
pub fn get_latest(&self, handle: &MyHandle) -> WalStoreResult<Arc<T>> {
let handle_lock = self.locks.for_handle(handle.clone());
let _read = handle_lock.read();
self.get_latest_no_lock(handle)
}
fn get_latest_no_lock(&self, handle: &MyHandle) -> WalStoreResult<Arc<T>> {
let mut instance = match self.cache.read().unwrap().get(handle).cloned() {
None => Arc::new(self.get_snapshot(handle)?),
Some(instance) => instance,
};
if !self.kv.has(&Self::key_for_wal_set(handle, instance.revision()))? {
Ok(instance)
} else {
let instance = Arc::make_mut(&mut instance);
loop {
let wal_set_key = Self::key_for_wal_set(handle, instance.revision());
if let Some(set) = self.kv.get(&wal_set_key)? {
instance.apply(set)
} else {
break;
}
}
let instance = Arc::new(instance.clone());
self.cache.write().unwrap().insert(handle.clone(), instance.clone());
Ok(instance)
}
}
pub fn remove(&self, handle: &MyHandle) -> WalStoreResult<()> {
if !self.has(handle)? {
Err(WalStoreError::Unknown(handle.clone()))
} else {
{
let handle_lock = self.locks.for_handle(handle.clone());
let _write = handle_lock.write();
self.cache.write().unwrap().remove(handle);
self.kv.drop_scope(handle.as_str())?;
}
self.locks.drop_handle(handle);
Ok(())
}
}
fn get_snapshot(&self, handle: &MyHandle) -> WalStoreResult<T> {
self.kv
.get(&Self::key_for_snapshot(handle))?
.ok_or_else(|| WalStoreError::Unknown(handle.clone()))
}
pub fn list(&self) -> WalStoreResult<Vec<MyHandle>> {
let mut res = vec![];
for scope in self.kv.scopes()? {
if let Ok(handle) = MyHandle::from_str(&scope) {
res.push(handle)
}
}
Ok(res)
}
pub fn send_command(&self, command: T::Command) -> Result<Arc<T>, T::Error> {
let handle = command.handle().clone();
let handle_lock = self.locks.for_handle(handle.clone());
let _write = handle_lock.write();
let mut latest = self.get_latest_no_lock(&handle)?;
let summary = command.to_string();
let revision = latest.revision();
let changes = latest.process_command(command)?;
if changes.is_empty() {
debug!("No changes need for '{}' when processing command: {}", handle, summary);
Ok(latest)
} else {
let mut cache = self.cache.write().unwrap();
let set: WalSet<T> = WalSet {
revision,
summary,
changes,
};
let key_for_wal_set = Self::key_for_wal_set(&handle, revision);
self.kv
.store_new(&key_for_wal_set, &set)
.map_err(WalStoreError::KeyStoreError)?;
let latest = Arc::make_mut(&mut latest);
latest.apply(set);
let latest = Arc::new(latest.clone());
cache.insert(handle, latest.clone());
Ok(latest)
}
}
pub fn update_snapshot(&self, handle: &MyHandle, archive: bool) -> WalStoreResult<()> {
let latest = self.get_latest(handle)?;
let key = Self::key_for_snapshot(handle);
self.kv.store(&key, &latest)?;
for key in self.kv.keys(Some(handle.to_string()), "wal-")? {
if let Some(remaining) = key.name().strip_prefix("wal-") {
if let Some(number) = remaining.strip_suffix(".json") {
if let Ok(revision) = u64::from_str(number) {
if revision < latest.revision() {
if archive {
self.kv.archive(&key)?;
} else {
self.kv.drop_key(&key)?;
}
}
}
}
}
}
Ok(())
}
fn key_for_snapshot(handle: &MyHandle) -> KeyStoreKey {
KeyStoreKey::scoped(handle.to_string(), "snapshot.json".to_string())
}
fn key_for_wal_set(handle: &MyHandle, revision: u64) -> KeyStoreKey {
KeyStoreKey::scoped(handle.to_string(), format!("wal-{}.json", revision))
}
}
pub type WalStoreResult<T> = Result<T, WalStoreError>;
#[derive(Debug)]
pub enum WalStoreError {
KeyStoreError(KeyValueError),
Unknown(MyHandle),
WarmupFailed(MyHandle, String),
}
impl From<KeyValueError> for WalStoreError {
fn from(e: KeyValueError) -> Self {
WalStoreError::KeyStoreError(e)
}
}
impl fmt::Display for WalStoreError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
WalStoreError::KeyStoreError(e) => write!(f, "KeyStore Error: {}", e),
WalStoreError::Unknown(handle) => write!(f, "Unknown entity: {}", handle),
WalStoreError::WarmupFailed(handle, e) => write!(f, "Warmup failed with entity '{}' error: {}", handle, e),
}
}
}