Skip to main content

krill/server/properties/
mod.rs

1//! Runtime configuration settings and properties
2//!
3//! Uses an event-sourced structure so that:
4//! - These can be shared between multiple nodes
5//! - An audit trail is provided
6//!
7//! For now this is used to track the current Krill version for
8//! the stored data in one place only. This will also allow multiple
9//! Krill instances to compare their own version to the shared storage.
10//! This can then be used to trigger upgrades. Furthermore, we can use
11//! this to stop (or refuse to start) instances that are behind the
12//! the storage version.
13//!
14//! In future we can extend this structure to include other runtime
15//! properties that need to be shared between nodes. E.g. timing
16//! parameters used for issuing certificates and ROAs etc.
17
18use std::{fmt, str::FromStr, sync::Arc};
19
20use log::{log_enabled, trace};
21use rpki::ca::idexchange::MyHandle;
22use serde::{Deserialize, Serialize};
23use url::Url;
24
25use crate::{
26    commons::{
27        actor::Actor,
28        error::Error,
29        eventsourcing::{
30            self, Aggregate, AggregateStore, Event, InitCommandDetails,
31            InitEvent, SentCommand, SentInitCommand, WithStorableDetails,
32        },
33        version::KrillVersion,
34        KrillResult,
35    },
36    constants::{ACTOR_DEF_KRILL, PROPERTIES_DFLT_NAME, PROPERTIES_NS},
37};
38use crate::api::history::CommandSummary;
39
40
41//------------ PropertiesInitCommand -----------------------------------------
42
43pub type PropertiesInitCommand =
44    SentInitCommand<PropertiesInitCommandDetails>;
45
46
47//------------ PropertiesInitCommandDetails ----------------------------------
48
49#[derive(Clone, Deserialize, Eq, PartialEq, Serialize)]
50pub struct PropertiesInitCommandDetails {
51    pub krill_version: KrillVersion,
52}
53
54impl fmt::Display for PropertiesInitCommandDetails {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        self.store().fmt(f)
57    }
58}
59
60impl InitCommandDetails for PropertiesInitCommandDetails {
61    type StorableDetails = StorablePropertiesCommand;
62
63    fn store(&self) -> Self::StorableDetails {
64        StorablePropertiesCommand::make_init()
65    }
66}
67
68
69//------------ PropertiesCommand ---------------------------------------------
70
71pub type PropertiesCommand = SentCommand<PropertiesCommandDetails>;
72
73
74//------------ PropertiesCommandDetails --------------------------------------
75
76#[derive(Clone, Debug)]
77pub enum PropertiesCommandDetails {
78    UpgradeTo { krill_version: KrillVersion },
79}
80
81impl fmt::Display for PropertiesCommandDetails {
82    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
83        StorablePropertiesCommand::from(self).fmt(f)
84    }
85}
86
87
88//------------ StorablePropertiesCommand -------------------------------------
89
90//  *Warning:* This type is used in stored state.
91#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
92#[serde(rename_all = "snake_case")]
93#[serde(tag = "type")]
94pub enum StorablePropertiesCommand {
95    Init,
96    UpgradeTo { krill_version: KrillVersion },
97}
98
99impl fmt::Display for StorablePropertiesCommand {
100    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
101        match self {
102            Self::Init => {
103                write!(f, "initialise properties")
104            }
105            Self::UpgradeTo {
106                krill_version: version,
107            } => {
108                write!(f, "upgrade Krill to {version}")
109            }
110        }
111    }
112}
113
114impl eventsourcing::CommandDetails for PropertiesCommandDetails {
115    type Event = PropertiesEvent;
116
117    type StorableDetails = StorablePropertiesCommand;
118
119    fn store(&self) -> Self::StorableDetails {
120        self.into()
121    }
122}
123
124impl From<&PropertiesCommandDetails> for StorablePropertiesCommand {
125    fn from(details: &PropertiesCommandDetails) -> Self {
126        match details {
127            PropertiesCommandDetails::UpgradeTo { krill_version } => {
128                StorablePropertiesCommand::UpgradeTo {
129                    krill_version: krill_version.clone(),
130                }
131            }
132        }
133    }
134}
135
136impl eventsourcing::WithStorableDetails for StorablePropertiesCommand {
137    fn summary(&self) -> crate::api::history::CommandSummary {
138        match self {
139            StorablePropertiesCommand::Init => {
140                CommandSummary::new("cmd-properties-init", self)
141            }
142            StorablePropertiesCommand::UpgradeTo { krill_version } => {
143                CommandSummary::new("cmd-properties-krill-upgrade", self)
144                    .arg("version", krill_version)
145            }
146        }
147    }
148
149    fn make_init() -> Self {
150        Self::Init
151    }
152}
153
154
155//------------ PropertiesEvent -----------------------------------------------
156
157//  *Warning:* This type is used in stored state.
158#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
159#[serde(rename_all = "snake_case")]
160#[serde(tag = "type")]
161pub enum PropertiesEvent {
162    KrillVersionUpgraded {
163        old: KrillVersion,
164        new: KrillVersion,
165    },
166}
167
168impl Event for PropertiesEvent {}
169
170impl fmt::Display for PropertiesEvent {
171    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
172        match self {
173            PropertiesEvent::KrillVersionUpgraded { old, new } => {
174                write!(f, "upgraded Krill from {old} to {new}")
175            }
176        }
177    }
178}
179
180
181//------------ PropertiesInitEvent -------------------------------------------
182
183//  *Warning:* This type is used in stored state.
184#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
185pub struct PropertiesInitEvent {
186    krill_version: KrillVersion,
187}
188
189impl InitEvent for PropertiesInitEvent {}
190
191impl fmt::Display for PropertiesInitEvent {
192    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
193        write!(f, "initialised Krill version {}", self.krill_version)
194    }
195}
196
197
198//------------ Properties ----------------------------------------------------
199
200/// Runtime properties used by the server
201///
202//  *Warning:* This type is used in stored state.
203#[derive(Clone, Debug, Deserialize, Serialize)]
204pub struct Properties {
205    handle: MyHandle,
206    version: u64,
207
208    krill_version: KrillVersion,
209}
210
211impl Aggregate for Properties {
212    type Command = PropertiesCommand;
213    type StorableCommandDetails = StorablePropertiesCommand;
214    type Event = PropertiesEvent;
215
216    type InitCommand = PropertiesInitCommand;
217    type InitEvent = PropertiesInitEvent;
218
219    type Error = Error;
220
221    fn init(handle: &MyHandle, event: PropertiesInitEvent) -> Self {
222        Properties {
223            handle: handle.clone(),
224            version: 1, // init for 0 was applied
225            krill_version: event.krill_version,
226        }
227    }
228
229    fn process_init_command(
230        command: PropertiesInitCommand,
231    ) -> Result<Self::InitEvent, Self::Error> {
232        Ok(PropertiesInitEvent {
233            krill_version: command.into_details().krill_version,
234        })
235    }
236
237    fn version(&self) -> u64 {
238        self.version
239    }
240
241    fn increment_version(&mut self) {
242        self.version += 1;
243    }
244
245    fn apply(&mut self, event: Self::Event) {
246        match event {
247            PropertiesEvent::KrillVersionUpgraded { new, .. } => {
248                self.krill_version = new
249            }
250        }
251    }
252
253    fn process_command(
254        &self,
255        command: Self::Command,
256    ) -> Result<Vec<Self::Event>, Self::Error> {
257        if log_enabled!(log::Level::Trace) {
258            trace!(
259                "Sending command to Properties '{}', version: {}: {}",
260                self.handle,
261                self.version,
262                command
263            );
264        }
265
266        match command.into_details() {
267            PropertiesCommandDetails::UpgradeTo { krill_version } => {
268                // We can only upgrade to a newer version.
269                if krill_version > self.krill_version {
270                    Ok(vec![PropertiesEvent::KrillVersionUpgraded {
271                        old: self.krill_version.clone(),
272                        new: krill_version,
273                    }])
274                } else {
275                    Err(Error::Custom(format!(
276                        "Can only upgrade Krill to newer versions. Current version: {}, Requested version: {}",
277                        self.krill_version, krill_version
278                    )))
279                }
280            }
281        }
282    }
283}
284
285//------------ PropertiesManager ---------------------------------------------
286
287/// Convenience manager for the single Properties instance used by Krill
288pub struct PropertiesManager {
289    store: AggregateStore<Properties>,
290    main_key: MyHandle,
291
292    // System actor is used for (scheduled or triggered) system actions where
293    // we have no operator actor context.
294    system_actor: Actor,
295}
296
297impl PropertiesManager {
298    pub fn create(
299        storage_uri: &Url,
300        use_history_cache: bool,
301    ) -> KrillResult<Self> {
302        let main_key = MyHandle::from_str(PROPERTIES_DFLT_NAME).unwrap();
303        AggregateStore::create(storage_uri, PROPERTIES_NS, use_history_cache)
304            .map(|store| PropertiesManager {
305                store,
306                main_key,
307                system_actor: ACTOR_DEF_KRILL,
308            })
309            .map_err(Error::AggregateStoreError)
310    }
311
312    pub fn is_initialized(&self) -> bool {
313        self.store.has(&self.main_key).unwrap_or_default()
314    }
315
316    pub fn init(
317        &self,
318        krill_version: KrillVersion,
319    ) -> KrillResult<Arc<Properties>> {
320        let cmd = PropertiesInitCommand::new(
321            self.main_key.clone(),
322            PropertiesInitCommandDetails { krill_version },
323            &self.system_actor,
324        );
325        self.store.add(cmd)
326    }
327
328    /// Returns the current KrillVersion used for the data store
329    pub fn current_krill_version(&self) -> KrillResult<KrillVersion> {
330        self.properties().map(|p| p.krill_version.clone())
331    }
332
333    /// Upgrade the KrillVersion
334    pub fn upgrade_krill_version(
335        &self,
336        krill_version: KrillVersion,
337    ) -> KrillResult<()> {
338        let cmd = PropertiesCommand::new(
339            self.main_key.clone(),
340            None,
341            PropertiesCommandDetails::UpgradeTo { krill_version },
342            &self.system_actor,
343        );
344        self.store.command(cmd)?;
345        Ok(())
346    }
347
348    fn properties(&self) -> KrillResult<Arc<Properties>> {
349        self.store.get_latest(&self.main_key)
350    }
351}
352
353
354//============ Tests =========================================================
355
356#[cfg(test)]
357mod tests {
358    use crate::commons::test;
359    use super::*;
360
361    #[test]
362    fn init_properties() {
363        test::test_in_memory(|storage_uri| {
364            let properties_mgr =
365                PropertiesManager::create(storage_uri, false).unwrap();
366
367            // Should not be initialised on first use.
368            assert!(!properties_mgr.is_initialized());
369
370            // We can initialise the properties to a given krill release.
371            let init_version = KrillVersion::release(0, 13, 0);
372            properties_mgr.init(init_version.clone()).unwrap();
373            assert_eq!(
374                init_version,
375                properties_mgr.current_krill_version().unwrap()
376            );
377
378            // Then we can upgrade the release
379            let updated_version = KrillVersion::release(0, 14, 0);
380            properties_mgr
381                .upgrade_krill_version(updated_version.clone())
382                .unwrap();
383            assert_eq!(
384                updated_version,
385                properties_mgr.current_krill_version().unwrap()
386            );
387
388            // We cannot downgrade
389            let downgrade_version = KrillVersion::release(0, 13, 99);
390            assert!(
391                downgrade_version
392                    < properties_mgr.current_krill_version().unwrap()
393            );
394            assert!(properties_mgr
395                .upgrade_krill_version(downgrade_version)
396                .is_err());
397        })
398    }
399}
400