orca/
state.rs

1use crate::error::Error;
2use crate::model::{ActorModel, ActorRole};
3use crate::models;
4use crate::profile::Profile;
5use core::fmt::Display;
6use kanidm_client::KanidmClient;
7use rand::SeedableRng;
8use rand_chacha::ChaCha8Rng;
9use serde::{Deserialize, Serialize};
10use std::collections::BTreeSet;
11use std::path::Path;
12use std::time::Duration;
13/// A serialisable state representing the content of a kanidm database and potential
14/// test content that can be created and modified.
15///
16/// This is all generated ahead of time before the test so that during the test
17/// as minimal calculation as possible is required.
18#[derive(Debug, Serialize, Deserialize)]
19pub struct State {
20    pub profile: Profile,
21    // ----------------------------
22    pub preflight_flags: Vec<Flag>,
23    pub persons: Vec<Person>,
24    pub groups: Vec<Group>,
25    pub thread_count: Option<usize>, // oauth_clients: Vec<Oauth2Clients>,
26}
27
28impl State {
29    pub fn write_to_path(&self, path: &Path) -> Result<(), Error> {
30        let output = std::fs::File::create(path).map_err(|io_err| {
31            error!(?io_err);
32            Error::Io
33        })?;
34
35        serde_json::to_writer(output, self).map_err(|json_err| {
36            error!(?json_err);
37            Error::SerdeJson
38        })
39    }
40}
41
42impl TryFrom<&Path> for State {
43    type Error = Error;
44
45    fn try_from(path: &Path) -> Result<Self, Self::Error> {
46        let input = std::fs::File::open(path).map_err(|io_err| {
47            error!(?io_err);
48            Error::Io
49        })?;
50
51        serde_json::from_reader(input).map_err(|json_err| {
52            error!(?json_err);
53            Error::SerdeJson
54        })
55    }
56}
57
58#[derive(Debug, Serialize, Deserialize)]
59pub enum Flag {
60    DisableAllPersonsMFAPolicy,
61    ExtendPrivilegedAuthExpiry,
62}
63
64#[derive(Default, Debug, Serialize, Deserialize)]
65pub enum PreflightState {
66    #[default]
67    Present,
68    Absent,
69}
70
71/// A model defines *how* an actors makes it's choices. For example the choices
72/// could be purely random, they could be a linear pattern, or they could have
73/// some set of weights related to choices they make.
74///
75/// Some models can *restrict* the set of choices that an actor may make.
76///
77/// This compliments ActorRoles, which define the extended actions an Actor may
78/// choose to perform. If ActorRoles are present, the model MAY choose to use
79/// these roles to perform extended operations.
80#[derive(clap::ValueEnum, Debug, Serialize, Deserialize, Clone, Default, Copy)]
81#[serde(rename_all = "snake_case")]
82pub enum Model {
83    /// This is a "hardcoded" model that just authenticates and searches
84    AuthOnly,
85    /// A simple linear executor that does actions in a loop.
86    #[default]
87    Basic,
88    /// This model only performs read requests in a loop
89    Reader,
90    /// This model only performs write requests in a loop
91    Writer,
92    /// This model adds empty group to a sever and measures how long it takes before they are replicated to the other servers
93    LatencyMeasurer,
94}
95
96impl Model {
97    pub fn as_dyn_object(
98        self,
99        rng_seed: u64,
100        additional_clients: Vec<KanidmClient>,
101        person_name: &str,
102        warmup_time: Duration,
103    ) -> Result<Box<dyn ActorModel + Send + '_>, Error> {
104        let cha_rng = ChaCha8Rng::seed_from_u64(rng_seed);
105        let warmup_time_as_ms = warmup_time.as_millis() as u64;
106        Ok(match self {
107            Model::AuthOnly => Box::new(models::auth_only::ActorAuthOnly::new()),
108            Model::Basic => Box::new(models::basic::ActorBasic::new(cha_rng, warmup_time_as_ms)),
109            Model::Reader => Box::new(models::read::ActorReader::new(cha_rng, warmup_time_as_ms)),
110            Model::Writer => Box::new(models::write::ActorWriter::new(cha_rng, warmup_time_as_ms)),
111            Model::LatencyMeasurer => {
112                Box::new(models::latency_measurer::ActorLatencyMeasurer::new(
113                    cha_rng,
114                    additional_clients,
115                    person_name,
116                    warmup_time_as_ms,
117                )?)
118            }
119        })
120    }
121}
122
123#[derive(Debug, Serialize, Deserialize)]
124pub enum Credential {
125    Password { plain: String },
126}
127
128#[derive(Debug, Serialize, Deserialize)]
129pub struct Person {
130    pub preflight_state: PreflightState,
131    pub username: String,
132    pub display_name: String,
133    pub roles: BTreeSet<ActorRole>,
134    pub credential: Credential,
135    pub model: Model,
136}
137
138#[derive(Default, Debug, Serialize, Deserialize)]
139pub struct Group {
140    pub name: GroupName,
141    pub preflight_state: PreflightState,
142    pub role: ActorRole,
143    pub members: BTreeSet<String>,
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize, Hash, Default, Ord, Eq, PartialEq, PartialOrd)]
147#[serde(rename_all = "snake_case")]
148#[allow(clippy::enum_variant_names)]
149pub enum GroupName {
150    RolePeopleSelfSetPassword,
151    #[default]
152    RolePeoplePiiReader,
153    RolePeopleSelfMailWrite,
154    RolePeopleSelfReadProfile,
155    RolePeopleSelfReadMemberOf,
156    RolePeopleGroupAdmin,
157}
158
159impl Display for GroupName {
160    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161        write!(
162            f,
163            "{}",
164            toml::to_string(self)
165                .expect("Failed to parse group name as string")
166                .trim_matches('"')
167        )
168    }
169}
170
171impl TryFrom<&String> for GroupName {
172    type Error = toml::de::Error;
173
174    fn try_from(value: &String) -> Result<Self, Self::Error> {
175        toml::from_str(&format!("\"{value}\""))
176    }
177}
178
179#[cfg(test)]
180mod test {
181
182    use super::GroupName;
183
184    #[test]
185    fn test_group_names_parsing() {
186        let group_names = vec![
187            GroupName::RolePeopleGroupAdmin,
188            GroupName::RolePeoplePiiReader,
189            GroupName::RolePeopleSelfReadMemberOf,
190        ];
191        for name in group_names {
192            let str = name.to_string();
193            let parsed_group_name = GroupName::try_from(&str).expect("Failed to parse group name");
194
195            assert_eq!(parsed_group_name, name);
196            dbg!(str);
197        }
198    }
199
200    #[test]
201    fn test_group_name_from_str() {
202        let group_admin = "role_people_group_admin";
203        assert_eq!(
204            GroupName::RolePeopleGroupAdmin,
205            GroupName::try_from(&group_admin.to_string()).unwrap()
206        )
207    }
208}