orca/models/
write.rs
1use crate::model::{self, ActorModel, Transition, TransitionAction, TransitionResult};
2
3use crate::error::Error;
4use crate::run::EventRecord;
5use crate::state::*;
6use kanidm_client::KanidmClient;
7
8use async_trait::async_trait;
9use rand::Rng;
10use rand_chacha::ChaCha8Rng;
11
12use std::time::Duration;
13
14enum State {
15 Unauthenticated,
16 Authenticated,
17 AuthenticatedWithReauth,
18}
19
20pub struct ActorWriter {
21 state: State,
22 randomised_backoff_time: Duration,
23}
24
25impl ActorWriter {
26 pub fn new(mut cha_rng: ChaCha8Rng, warmup_time_ms: u64) -> Self {
27 let max_backoff_time_in_ms = 2 * warmup_time_ms / 3;
28 let randomised_backoff_time =
29 Duration::from_millis(cha_rng.random_range(0..max_backoff_time_in_ms));
30 ActorWriter {
31 state: State::Unauthenticated,
32 randomised_backoff_time,
33 }
34 }
35}
36
37#[async_trait]
38impl ActorModel for ActorWriter {
39 async fn transition(
40 &mut self,
41 client: &KanidmClient,
42 person: &Person,
43 ) -> Result<Vec<EventRecord>, Error> {
44 let transition = self.next_transition();
45
46 if let Some(delay) = transition.delay {
47 tokio::time::sleep(delay).await;
48 }
49
50 let (result, event) = match transition.action {
52 TransitionAction::Login => model::login(client, person).await,
53 TransitionAction::Logout => model::logout(client, person).await,
54 TransitionAction::PrivilegeReauth => model::privilege_reauth(client, person).await,
55 TransitionAction::ReadSelfMemberOf
56 | TransitionAction::ReadSelfAccount
57 | TransitionAction::WriteSelfPassword => return Err(Error::InvalidState),
58 TransitionAction::WriteAttributePersonMail => {
59 let mail = format!("{}@example.com", person.username);
60 let values = &[mail.as_str()];
61 model::person_set_self_mail(client, person, values).await
62 }
63 }?;
64
65 self.next_state(transition.action, result);
66
67 Ok(event)
68 }
69}
70
71impl ActorWriter {
72 fn next_transition(&mut self) -> Transition {
73 match self.state {
74 State::Unauthenticated => Transition {
75 delay: Some(self.randomised_backoff_time),
76 action: TransitionAction::Login,
77 },
78 State::Authenticated => Transition {
79 delay: Some(Duration::from_secs(2)),
80 action: TransitionAction::PrivilegeReauth,
81 },
82 State::AuthenticatedWithReauth => Transition {
83 delay: Some(Duration::from_secs(1)),
84 action: TransitionAction::WriteAttributePersonMail,
85 },
86 }
87 }
88
89 fn next_state(&mut self, action: TransitionAction, result: TransitionResult) {
90 match (&self.state, action, result) {
93 (State::Unauthenticated, TransitionAction::Login, TransitionResult::Ok) => {
94 self.state = State::Authenticated;
95 }
96 (State::Authenticated, TransitionAction::PrivilegeReauth, TransitionResult::Ok) => {
97 self.state = State::AuthenticatedWithReauth;
98 }
99 (
100 State::AuthenticatedWithReauth,
101 TransitionAction::WriteAttributePersonMail,
102 TransitionResult::Ok,
103 ) => self.state = State::AuthenticatedWithReauth,
104
105 #[allow(clippy::unreachable)]
106 (_, _, TransitionResult::Ok) => {
107 unreachable!();
108 }
109 (_, _, TransitionResult::Error) => {
110 self.state = State::Unauthenticated;
111 }
112 }
113 }
114}