use std::{error, fmt};
use std::borrow::Cow;
use std::collections::HashMap;
use std::str::FromStr;
use std::sync::{Arc, RwLock};
use log::{error, warn, trace};
use rpki::ca::idexchange::MyHandle;
use serde::{Deserialize, Serialize};
use url::Url;
use crate::commons::storage::{Ident, KeyValueError, KeyValueStore};
use super::store::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: Clone + 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>>>,
}
impl<T: WalSupport> WalStore<T> {
pub fn create(
storage_uri: &Url,
namespace: &Ident,
) -> Result<Self, WalStoreError> {
Ok(WalStore {
kv: KeyValueStore::create(storage_uri, namespace)?,
cache: RwLock::new(HashMap::new()),
})
}
pub fn warm(&self) -> Result<(), WalStoreError> {
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
) -> Result<(), WalStoreError> {
let scope = Self::scope_for_handle(handle);
let instance = Arc::new(instance);
self.kv.execute(Some(&scope), |kv| {
kv.store(
Some(&scope), Self::key_for_snapshot(),
instance.as_ref()
)?;
self.cache_update(handle, instance.clone());
Ok(())
}).map_err(WalStoreError::KeyStoreError)
}
pub fn has(&self, handle: &MyHandle) -> Result<bool, WalStoreError> {
self.kv.has_scope(
&Self::scope_for_handle(handle)
).map_err(WalStoreError::KeyStoreError)
}
pub fn get_latest(&self, handle: &MyHandle) -> Result<Arc<T>, T::Error> {
self.execute_opt_command(handle, None, false)
}
pub fn remove(&self, handle: &MyHandle) -> Result<(), WalStoreError> {
let scope = Self::scope_for_handle(handle);
if !self.kv.has_scope(&scope)? {
Err(WalStoreError::Unknown(handle.clone()))
}
else {
self.kv.execute(Some(&scope), |kv| {
kv.delete_scope(&scope)
}).map_err(WalStoreError::KeyStoreError)?;
self.cache_remove(handle);
Ok(())
}
}
pub fn list(&self) -> Result<Vec<MyHandle>, WalStoreError> {
let mut res = vec![];
for scope in self.kv.scopes()? {
if let Ok(handle) = MyHandle::from_str(&scope.to_string()) {
res.push(handle)
}
}
Ok(res)
}
pub fn send_command(
&self,
command: T::Command,
) -> Result<Arc<T>, T::Error> {
let handle = command.handle().clone();
self.execute_opt_command(&handle, Some(command), false)
}
pub fn update_snapshots(&self) -> Result<(), T::Error> {
for handle in self.list()? {
self.update_snapshot(&handle)?;
}
Ok(())
}
pub fn update_snapshot(
&self,
handle: &MyHandle,
) -> Result<Arc<T>, T::Error> {
self.execute_opt_command(handle, None, true)
}
fn execute_opt_command(
&self,
handle: &MyHandle,
cmd_opt: Option<T::Command>,
save_snapshot: bool,
) -> Result<Arc<T>, T::Error> {
let scope = Self::scope_for_handle(handle);
self.kv.execute(Some(&scope), |kv| {
let mut changed_from_cached = false;
let mut latest = match self.cache_get(handle) {
Some(t) => {
trace!(
"Found cached instance for '{handle}', \
at revision: {}",
t.revision()
);
t
}
None => {
trace!("No cached instance found for '{handle}'");
changed_from_cached = true;
match kv.get(Some(&scope), Self::key_for_snapshot())? {
Some(value) => {
trace!(
"Deserializing stored instance for '{handle}'"
);
Arc::new(value)
}
None => {
trace!(
"No instance found instance for '{handle}'"
);
return Ok(Err(T::Error::from(
WalStoreError::Unknown(handle.clone())
)));
}
}
}
};
{
let latest_inner = Arc::make_mut(&mut latest);
while let Some(value) = kv.get(
Some(&scope),
&Self::key_for_wal_set(latest_inner.revision())
)? {
trace!("applying revision '{handle}'");
latest_inner.apply(value);
changed_from_cached = true;
}
if let Some(command) = cmd_opt.clone() {
let summary = command.to_string();
let revision = latest_inner.revision();
trace!("Applying command {command} to {handle}");
match latest_inner.process_command(command) {
Err(e) => {
warn!(
"Command '{summary}' for '{handle}' \
failed. Error: '{e}'"
);
return Ok(Err(e));
}
Ok(changes) => {
if changes.is_empty() {
trace!(
"No changes needed for '{handle}' when \
processing command: {summary}",
);
}
else {
trace!(
"{} changes resulted for '{}' when \
processing command: {}",
changes.len(), handle, summary,
);
changed_from_cached = true;
let set: WalSet<T> = WalSet {
revision, summary, changes,
};
let key_for_wal_set = Self::key_for_wal_set(
revision
);
if kv.has(Some(&scope), &key_for_wal_set)? {
error!(
"Change set for '{handle}' version \
'{revision}' already exists."
);
error!(
"This is a bug. Please report this \
issue to rpki-team@nlnetlabs.nl."
);
error!(
"Krill will exit. If this issue \
repeats, consider removing {handle}."
);
std::process::exit(1);
}
latest_inner.apply(set.clone());
kv.store(
Some(&scope), &key_for_wal_set, &set
)?;
}
}
}
}
}
if changed_from_cached {
self.cache_update(handle, latest.clone());
}
if save_snapshot {
kv.store(
Some(&scope), Self::key_for_snapshot(), latest.as_ref()
)?;
for key in kv.list_keys(Some(&scope))? {
if key.as_str().starts_with("wal-") {
kv.delete(Some(&scope), &key)?;
}
}
}
Ok(Ok(latest))
}).map_err(|e| T::Error::from(WalStoreError::KeyStoreError(e)))?
}
}
impl<T: WalSupport> WalStore<T> {
fn cache_get(&self, id: &MyHandle) -> Option<Arc<T>> {
self.cache.read().unwrap().get(id).cloned()
}
fn cache_remove(&self, id: &MyHandle) {
self.cache.write().unwrap().remove(id);
}
fn cache_update(&self, id: &MyHandle, arc: Arc<T>) {
self.cache.write().unwrap().insert(id.clone(), arc);
}
}
impl<T: WalSupport> WalStore<T> {
fn scope_for_handle(handle: &MyHandle) -> Cow<'_, Ident> {
Ident::from_handle(handle)
}
const fn key_for_snapshot() -> &'static Ident {
const { Ident::make("snapshot.json") }
}
fn key_for_wal_set(revision: u64) -> Box<Ident> {
Ident::builder(
const { Ident::make("wal-") }
).push_u64(
revision
).finish_with_extension(
const { Ident::make("json") }
)
}
}
#[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 '{handle}' error: {e}"
),
}
}
}
impl error::Error for WalStoreError { }