use std::{error, fmt};
use std::borrow::Cow;
use std::collections::HashMap;
use std::str::FromStr;
use std::sync::{Arc, Mutex, RwLock};
use log::{error, trace};
use rpki::ca::idexchange::MyHandle;
use rpki::repository::x509::Time;
use serde::Serialize;
use serde::de::DeserializeOwned;
use url::Url;
use crate::api::history::{
CommandHistory, CommandHistoryCriteria, CommandHistoryRecord
};
use crate::commons::error::KrillIoError;
use crate::commons::storage::{Ident, KeyValueError, KeyValueStore};
use super::agg::{
Aggregate, Command, InitCommand, PostSaveEventListener,
PreSaveEventListener, StoredCommand
};
pub trait Storable: Clone + Serialize + DeserializeOwned { }
impl<T: Clone + Serialize + DeserializeOwned> Storable for T { }
pub struct AggregateStore<A: Aggregate> {
kv: KeyValueStore,
cache: RwLock<HashMap<MyHandle, Arc<A>>>,
history_cache: Option<Mutex<HashMap<MyHandle, Vec<CommandHistoryRecord>>>>,
pre_save_listeners: Vec<Arc<dyn PreSaveEventListener<A>>>,
post_save_listeners: Vec<Arc<dyn PostSaveEventListener<A>>>,
}
impl<A: Aggregate> AggregateStore<A> {
pub fn create(
storage_uri: &Url,
namespace: &Ident,
use_history_cache: bool,
) -> Result<Self, AggregateStoreError> {
Ok(Self::create_from_kv(
KeyValueStore::create(storage_uri, namespace)?, use_history_cache
))
}
pub fn create_upgrade_store(
storage_uri: &Url,
namespace: &Ident,
use_history_cache: bool,
) -> Result<Self, AggregateStoreError> {
Ok(Self::create_from_kv(
KeyValueStore::create_upgrade_store(storage_uri, namespace)?,
use_history_cache,
))
}
fn create_from_kv(
kv: KeyValueStore,
use_history_cache: bool,
) -> Self {
Self {
kv,
cache: RwLock::new(HashMap::new()),
history_cache: if use_history_cache {
Some(Mutex::new(HashMap::new()))
}
else {
None
},
pre_save_listeners: Vec::new(),
post_save_listeners: Vec::new(),
}
}
pub fn warm(&self) -> Result<(), AggregateStoreError> {
for handle in self.list()? {
self.get_latest(&handle).map_err(|e| {
AggregateStoreError::WarmupFailed(
handle.clone(), e.to_string()
)
})?;
}
Ok(())
}
pub fn add_pre_save_listener<L: PreSaveEventListener<A>>(
&mut self,
sync_listener: Arc<L>,
) {
self.pre_save_listeners.push(sync_listener);
}
pub fn add_post_save_listener<L: PostSaveEventListener<A>>(
&mut self,
listener: Arc<L>,
) {
self.post_save_listeners.push(listener);
}
}
impl<A: Aggregate> AggregateStore<A> {
pub fn has(&self, id: &MyHandle) -> Result<bool, AggregateStoreError> {
Ok(self.kv.has(
Some(&Self::scope_for_agg(id)), &Self::key_for_command(0)
)?)
}
pub fn list(&self) -> Result<Vec<MyHandle>, AggregateStoreError> {
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 get_latest(&self, handle: &MyHandle) -> Result<Arc<A>, A::Error> {
self.execute_opt_command(handle, None, false)
}
pub fn get_command(
&self,
id: &MyHandle,
version: u64,
) -> Result<StoredCommand<A>, AggregateStoreError> {
match self.kv.get(
Some(&Self::scope_for_agg(id)), &Self::key_for_command(version)
)? {
Some(cmd) => Ok(cmd),
None => {
Err(AggregateStoreError::CommandNotFound(id.clone(), version))
}
}
}
pub fn update_snapshots(&self) -> Result<(), A::Error> {
for handle in self.list()? {
self.save_snapshot(&handle)?;
}
Ok(())
}
pub fn save_snapshot(
&self, handle: &MyHandle
) -> Result<Arc<A>, A::Error> {
self.execute_opt_command(handle, None, true)
}
pub fn add(&self, cmd: A::InitCommand) -> Result<Arc<A>, A::Error> {
let scope = Self::scope_for_agg(cmd.handle());
self.kv.execute(Some(&scope), |kv| {
let init_command_key = Self::key_for_command(0);
if kv.has(Some(&scope), &init_command_key)? {
Ok(Err(A::Error::from(
AggregateStoreError::DuplicateAggregate(
cmd.handle().clone()
),
)))
}
else {
let processed_command_builder = StoredCommand::<A>::builder(
cmd.actor().to_string(),
Time::now(),
cmd.handle().clone(),
0,
cmd.store(),
);
match A::process_init_command(cmd.clone()) {
Ok(init_event) => {
let aggregate = A::init(
cmd.handle(), init_event.clone(),
);
let processed_command = processed_command_builder
.finish_with_init_event(init_event);
kv.store(
Some(&scope), &init_command_key,
&processed_command
)?;
let arc = Arc::new(aggregate);
self.cache_update(cmd.handle(), arc.clone());
Ok(Ok(arc))
}
Err(e) => Ok(Err(e)),
}
}
}).map_err(|e| {
A::Error::from(AggregateStoreError::KeyStoreError(e))
})?
}
pub fn command(&self, cmd: A::Command) -> Result<Arc<A>, A::Error> {
self.execute_opt_command(cmd.handle(), Some(&cmd), false)
}
fn execute_opt_command(
&self,
handle: &MyHandle,
cmd_opt: Option<&A::Command>,
save_snapshot: bool,
) -> Result<Arc<A>, A::Error> {
let scope = Self::scope_for_agg(handle);
self.kv.execute(Some(&scope), |kv| {
let mut changed_from_cached = false;
let mut agg = match self.cache_get(handle) {
Some(arc) => {
trace!("found cached snapshot for {handle}");
arc
}
None => {
changed_from_cached = true;
match kv.get(
Some(&scope), Self::key_for_snapshot()
)? {
Some(agg) => {
trace!("found snapshot for {handle}");
Arc::new(agg)
}
None => {
let init_key = Self::key_for_command(0);
match kv.get::<StoredCommand<A>>(
Some(&scope), &init_key
)? {
Some(init_command) => {
trace!("found init command for {handle}");
match init_command.into_init() {
Some(init_event) => {
let agg = A::init(
handle, init_event
);
Arc::new(agg)
}
None => {
return Ok(Err(A::Error::from(
AggregateStoreError::
UnknownAggregate(
handle.clone(),
)
)))
}
}
}
None => {
trace!(
"neither snapshot nor init \
command found for {handle}"
);
return Ok(Err(A::Error::from(
AggregateStoreError
::UnknownAggregate(
handle.clone()
)
)))
}
}
}
}
}
};
let next_command = Self::key_for_command(agg.version());
if kv.has(Some(&scope), &next_command)? {
let aggregate = Arc::make_mut(&mut agg);
loop {
let version = aggregate.version();
let key = Self::key_for_command(version);
match kv.get::<StoredCommand<A>>(Some(&scope), &key)? {
None => break,
Some(command) => {
trace!(
"found next command found for {handle}: {key}"
);
aggregate.apply_command(command);
changed_from_cached = true;
}
}
}
}
let res = if let Some(cmd) = cmd_opt {
let aggregate = Arc::make_mut(&mut agg);
let version = aggregate.version();
let processed = StoredCommand::<A>::builder(
cmd.actor().to_string(),
Time::now(),
cmd.handle().clone(),
version,
cmd.store(),
);
let command_key = Self::key_for_command(version);
if kv.has(Some(&scope), &command_key)? {
error!(
"Command key for '{handle}' version '{version}' \
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);
}
match aggregate.process_command(cmd.clone()) {
Err(e) => {
let processed = processed.finish_with_error(&e);
aggregate.apply_command(processed.clone());
changed_from_cached = true;
kv.store(Some(&scope), &command_key, &processed)?;
Err(e)
}
Ok(events) => {
if !events.is_empty() {
let processed = processed.finish_with_events(
events
);
aggregate.apply_command(processed.clone());
let mut opt_err: Option<A::Error> = None;
if let Some(events) = processed.events() {
for pre_save_listener
in &self.pre_save_listeners {
if let Err(e)
= pre_save_listener.as_ref()
.listen(aggregate, events)
{
opt_err = Some(e);
break;
}
}
}
if let Some(e) = opt_err {
changed_from_cached = false;
Err(e)
} else {
kv.store(
Some(&scope), &command_key, &processed
)?;
if let Some(events) = processed.events() {
for listener in &self.post_save_listeners {
listener.as_ref().listen(
aggregate, events
);
}
}
Ok(())
}
}
else {
Ok(())
}
}
}
}
else {
Ok(())
};
if changed_from_cached {
self.cache_update(handle, agg.clone());
}
if save_snapshot {
kv.store(
Some(&scope), Self::key_for_snapshot(),
agg.as_ref()
)?;
}
if let Err(e) = res {
Ok(Err(e))
}
else {
Ok(Ok(agg))
}
})
.map_err(|e| A::Error::from(AggregateStoreError::KeyStoreError(e)))?
}
pub fn drop_aggregate(
&self,
id: &MyHandle,
) -> Result<(), AggregateStoreError> {
let scope = Self::scope_for_agg(id);
self.kv.execute(Some(&scope), |kv| kv.delete_scope(&scope))?;
self.cache_remove(id);
Ok(())
}
}
impl<A: Aggregate> AggregateStore<A> {
pub fn command_history(
&self,
id: &MyHandle,
criteria: CommandHistoryCriteria,
) -> Result<CommandHistory, AggregateStoreError> {
match &self.history_cache {
Some(mutex) => {
let mut cache_lock = mutex.lock().unwrap();
let records = cache_lock.entry(id.clone()).or_default();
self.update_history_records(id, records)?;
Ok(Self::command_history_for_records(criteria, records))
}
None => {
let mut records = vec![];
self.update_history_records(id, &mut records)?;
Ok(Self::command_history_for_records(criteria, &records))
}
}
}
fn update_history_records(
&self,
id: &MyHandle,
records: &mut Vec<CommandHistoryRecord>,
) -> Result<(), AggregateStoreError> {
let mut version = match records.last() {
Some(record) => record.version + 1,
None => 1,
};
while let Ok(command) = self.get_command(id, version) {
records.push(command.into_history_record());
version += 1;
}
Ok(())
}
fn command_history_for_records(
criteria: CommandHistoryCriteria,
records: &[CommandHistoryRecord],
) -> CommandHistory {
let offset = criteria.offset;
let rows = match criteria.rows_limit {
Some(limit) => limit,
None => records.len(),
};
let mut commands = Vec::with_capacity(rows);
let mut skipped = 0;
let mut total = 0;
for record in records.iter() {
if record.matches(&criteria) {
total += 1;
if skipped < offset {
skipped += 1;
} else if total - skipped <= rows {
commands.push(record.clone());
}
}
}
CommandHistory { offset, total, commands }
}
}
impl<A: Aggregate> AggregateStore<A> {
fn cache_get(&self, id: &MyHandle) -> Option<Arc<A>> {
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<A>) {
self.cache.write().unwrap().insert(id.clone(), arc);
}
}
impl<A: Aggregate> AggregateStore<A> {
fn scope_for_agg(id: &MyHandle) -> Cow<'_, Ident> {
Ident::from_handle(id)
}
const fn key_for_snapshot() -> &'static Ident {
const { Ident::make("snapshot.json") }
}
fn key_for_command(version: u64) -> Box<Ident> {
Ident::builder(
const { Ident::make("command-") }
).push_u64(
version
).finish_with_extension(
const { Ident::make("json") }
)
}
}
#[derive(Debug)]
pub enum AggregateStoreError {
IoError(KrillIoError),
KeyStoreError(KeyValueError),
NotInitialized,
UnknownAggregate(MyHandle),
DuplicateAggregate(MyHandle),
InitError(MyHandle),
ReplayError(MyHandle, u64, u64),
ConcurrentModification(MyHandle),
UnknownCommand(MyHandle, u64),
WarmupFailed(MyHandle, String),
CouldNotArchive(MyHandle, String),
CommandCorrupt(MyHandle, u64),
CommandNotFound(MyHandle, u64),
}
impl From<KeyValueError> for AggregateStoreError {
fn from(e: KeyValueError) -> Self {
AggregateStoreError::KeyStoreError(e)
}
}
impl fmt::Display for AggregateStoreError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AggregateStoreError::IoError(e) => e.fmt(f),
AggregateStoreError::KeyStoreError(e) => {
write!(f, "KeyStore Error: {e}")
}
AggregateStoreError::NotInitialized => {
write!(f, "This aggregate store is not initialized")
}
AggregateStoreError::UnknownAggregate(handle) => {
write!(f, "unknown entity: {handle}")
}
AggregateStoreError::DuplicateAggregate(handle) => {
write!(f, "duplicate entity: {handle}")
}
AggregateStoreError::InitError(handle) => {
write!(f, "Command 0 for '{handle}' has no init")
}
AggregateStoreError::ReplayError(
handle,
version,
fail_version,
) => write!(
f,
"Event for '{handle}' version '{version}' had version '{fail_version}'"
),
AggregateStoreError::ConcurrentModification(handle) => {
write!(
f,
"concurrent modification attempt for entity: '{handle}'"
)
}
AggregateStoreError::UnknownCommand(handle, version) => write!(
f,
"Aggregate '{handle}' does not have command with version '{version}'"
),
AggregateStoreError::WarmupFailed(handle, e) => {
write!(f, "Could not rebuild state for '{handle}': {e}")
}
AggregateStoreError::CouldNotArchive(handle, e) => write!(
f,
"Could not archive commands and events for '{handle}'. Error: {e}"
),
AggregateStoreError::CommandCorrupt(handle, key) => {
write!(
f,
"StoredCommand '{handle}' for '{key}' was corrupt"
)
}
AggregateStoreError::CommandNotFound(handle, key) => {
write!(
f,
"StoredCommand '{handle}' for '{key}' cannot be found"
)
}
}
}
}
impl error::Error for AggregateStoreError { }