use std::{fmt, str::FromStr, sync::Arc};
use log::{log_enabled, trace};
use rpki::ca::idexchange::MyHandle;
use serde::{Deserialize, Serialize};
use url::Url;
use crate::{
commons::{
actor::Actor,
error::Error,
eventsourcing::{
self, Aggregate, AggregateStore, Event, InitCommandDetails,
InitEvent, SentCommand, SentInitCommand, WithStorableDetails,
},
version::KrillVersion,
KrillResult,
},
constants::{ACTOR_DEF_KRILL, PROPERTIES_DFLT_NAME, PROPERTIES_NS},
};
use crate::api::history::CommandSummary;
pub type PropertiesInitCommand =
SentInitCommand<PropertiesInitCommandDetails>;
#[derive(Clone, Deserialize, Eq, PartialEq, Serialize)]
pub struct PropertiesInitCommandDetails {
pub krill_version: KrillVersion,
}
impl fmt::Display for PropertiesInitCommandDetails {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.store().fmt(f)
}
}
impl InitCommandDetails for PropertiesInitCommandDetails {
type StorableDetails = StorablePropertiesCommand;
fn store(&self) -> Self::StorableDetails {
StorablePropertiesCommand::make_init()
}
}
pub type PropertiesCommand = SentCommand<PropertiesCommandDetails>;
#[derive(Clone, Debug)]
pub enum PropertiesCommandDetails {
UpgradeTo { krill_version: KrillVersion },
}
impl fmt::Display for PropertiesCommandDetails {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
StorablePropertiesCommand::from(self).fmt(f)
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "type")]
pub enum StorablePropertiesCommand {
Init,
UpgradeTo { krill_version: KrillVersion },
}
impl fmt::Display for StorablePropertiesCommand {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Init => {
write!(f, "initialise properties")
}
Self::UpgradeTo {
krill_version: version,
} => {
write!(f, "upgrade Krill to {version}")
}
}
}
}
impl eventsourcing::CommandDetails for PropertiesCommandDetails {
type Event = PropertiesEvent;
type StorableDetails = StorablePropertiesCommand;
fn store(&self) -> Self::StorableDetails {
self.into()
}
}
impl From<&PropertiesCommandDetails> for StorablePropertiesCommand {
fn from(details: &PropertiesCommandDetails) -> Self {
match details {
PropertiesCommandDetails::UpgradeTo { krill_version } => {
StorablePropertiesCommand::UpgradeTo {
krill_version: krill_version.clone(),
}
}
}
}
}
impl eventsourcing::WithStorableDetails for StorablePropertiesCommand {
fn summary(&self) -> crate::api::history::CommandSummary {
match self {
StorablePropertiesCommand::Init => {
CommandSummary::new("cmd-properties-init", self)
}
StorablePropertiesCommand::UpgradeTo { krill_version } => {
CommandSummary::new("cmd-properties-krill-upgrade", self)
.arg("version", krill_version)
}
}
}
fn make_init() -> Self {
Self::Init
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "type")]
pub enum PropertiesEvent {
KrillVersionUpgraded {
old: KrillVersion,
new: KrillVersion,
},
}
impl Event for PropertiesEvent {}
impl fmt::Display for PropertiesEvent {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
PropertiesEvent::KrillVersionUpgraded { old, new } => {
write!(f, "upgraded Krill from {old} to {new}")
}
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct PropertiesInitEvent {
krill_version: KrillVersion,
}
impl InitEvent for PropertiesInitEvent {}
impl fmt::Display for PropertiesInitEvent {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "initialised Krill version {}", self.krill_version)
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Properties {
handle: MyHandle,
version: u64,
krill_version: KrillVersion,
}
impl Aggregate for Properties {
type Command = PropertiesCommand;
type StorableCommandDetails = StorablePropertiesCommand;
type Event = PropertiesEvent;
type InitCommand = PropertiesInitCommand;
type InitEvent = PropertiesInitEvent;
type Error = Error;
fn init(handle: &MyHandle, event: PropertiesInitEvent) -> Self {
Properties {
handle: handle.clone(),
version: 1, krill_version: event.krill_version,
}
}
fn process_init_command(
command: PropertiesInitCommand,
) -> Result<Self::InitEvent, Self::Error> {
Ok(PropertiesInitEvent {
krill_version: command.into_details().krill_version,
})
}
fn version(&self) -> u64 {
self.version
}
fn increment_version(&mut self) {
self.version += 1;
}
fn apply(&mut self, event: Self::Event) {
match event {
PropertiesEvent::KrillVersionUpgraded { new, .. } => {
self.krill_version = new
}
}
}
fn process_command(
&self,
command: Self::Command,
) -> Result<Vec<Self::Event>, Self::Error> {
if log_enabled!(log::Level::Trace) {
trace!(
"Sending command to Properties '{}', version: {}: {}",
self.handle,
self.version,
command
);
}
match command.into_details() {
PropertiesCommandDetails::UpgradeTo { krill_version } => {
if krill_version > self.krill_version {
Ok(vec![PropertiesEvent::KrillVersionUpgraded {
old: self.krill_version.clone(),
new: krill_version,
}])
} else {
Err(Error::Custom(format!(
"Can only upgrade Krill to newer versions. Current version: {}, Requested version: {}",
self.krill_version, krill_version
)))
}
}
}
}
}
pub struct PropertiesManager {
store: AggregateStore<Properties>,
main_key: MyHandle,
system_actor: Actor,
}
impl PropertiesManager {
pub fn create(
storage_uri: &Url,
use_history_cache: bool,
) -> KrillResult<Self> {
let main_key = MyHandle::from_str(PROPERTIES_DFLT_NAME).unwrap();
AggregateStore::create(storage_uri, PROPERTIES_NS, use_history_cache)
.map(|store| PropertiesManager {
store,
main_key,
system_actor: ACTOR_DEF_KRILL,
})
.map_err(Error::AggregateStoreError)
}
pub fn is_initialized(&self) -> bool {
self.store.has(&self.main_key).unwrap_or_default()
}
pub fn init(
&self,
krill_version: KrillVersion,
) -> KrillResult<Arc<Properties>> {
let cmd = PropertiesInitCommand::new(
self.main_key.clone(),
PropertiesInitCommandDetails { krill_version },
&self.system_actor,
);
self.store.add(cmd)
}
pub fn current_krill_version(&self) -> KrillResult<KrillVersion> {
self.properties().map(|p| p.krill_version.clone())
}
pub fn upgrade_krill_version(
&self,
krill_version: KrillVersion,
) -> KrillResult<()> {
let cmd = PropertiesCommand::new(
self.main_key.clone(),
None,
PropertiesCommandDetails::UpgradeTo { krill_version },
&self.system_actor,
);
self.store.command(cmd)?;
Ok(())
}
fn properties(&self) -> KrillResult<Arc<Properties>> {
self.store.get_latest(&self.main_key)
}
}
#[cfg(test)]
mod tests {
use crate::commons::test;
use super::*;
#[test]
fn init_properties() {
test::test_in_memory(|storage_uri| {
let properties_mgr =
PropertiesManager::create(storage_uri, false).unwrap();
assert!(!properties_mgr.is_initialized());
let init_version = KrillVersion::release(0, 13, 0);
properties_mgr.init(init_version.clone()).unwrap();
assert_eq!(
init_version,
properties_mgr.current_krill_version().unwrap()
);
let updated_version = KrillVersion::release(0, 14, 0);
properties_mgr
.upgrade_krill_version(updated_version.clone())
.unwrap();
assert_eq!(
updated_version,
properties_mgr.current_krill_version().unwrap()
);
let downgrade_version = KrillVersion::release(0, 13, 99);
assert!(
downgrade_version
< properties_mgr.current_krill_version().unwrap()
);
assert!(properties_mgr
.upgrade_krill_version(downgrade_version)
.is_err());
})
}
}