Skip to content

Commit

Permalink
New orca models (kanidm#2909)
Browse files Browse the repository at this point in the history
* Orca is happy, everyone is happy
* added model selection option for Orca and properly included opt.rs inside the file tree instead of using `include!` macro
* added variable login delay to both read and write models!
* Clippy and William are happier this way
* fixed toml syntax for member count and removed old person_count_by_group inside ProfileBuilder
* added strong typing for group names, in the future this should help adding CLI support
* added the `latency_measurer` model and improved the ActorModel trait
* Fixed lots of bugs and made clippy happier
* updated all models to use random backoff time for the login transition
  • Loading branch information
Seba-T authored Jul 30, 2024
1 parent 3298eec commit 60e5b09
Show file tree
Hide file tree
Showing 18 changed files with 997 additions and 118 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions tools/orca/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ crossbeam = { workspace = true }
csv = { workspace = true }
futures-util = { workspace = true, features = ["sink"] }
hashbrown = { workspace = true }
idlset = { workspace = true }
kanidm_client = { workspace = true }
kanidm_proto = { workspace = true }
mathru = { workspace = true }
Expand Down
24 changes: 19 additions & 5 deletions tools/orca/profile-sample.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
control_uri = "https://localhost:8443"
admin_password = ""
idm_admin_password = ""
seed = -1236045086759770365
control_uri = "https://localhost:8443"
extra_uris = []
warmup_time = 10
test_time = 180
group_count = 5
idm_admin_password = ""
model = "writer"
person_count = 500
seed = -1236045086759770365
test_time = 60
thread_count = 20
warmup_time = 10

[group.role_people_self_mail_write]
member_count = 500
[group.role_people_self_set_password]
member_count = 0
[group.role_people_pii_reader]
member_count = 0
[group.role_people_self_read_profile]
member_count = 0
[group.role_people_self_read_member_of]
member_count = 0
[group.role_people_group_admin]
member_count = 0
51 changes: 35 additions & 16 deletions tools/orca/src/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use crate::error::Error;
use crate::kani::KanidmOrcaClient;
use crate::model::ActorRole;
use crate::profile::Profile;
use crate::state::{Credential, Flag, Group, Model, Person, PreflightState, State};
use crate::state::{Credential, Flag, Group, GroupName, Person, PreflightState, State};
use hashbrown::HashMap;
use rand::distributions::{Alphanumeric, DistString, Uniform};
use rand::seq::{index, SliceRandom};
use rand::{Rng, SeedableRng};
Expand Down Expand Up @@ -61,37 +62,44 @@ pub async fn populate(_client: &KanidmOrcaClient, profile: Profile) -> Result<St
// These decide what each person is supposed to do with their life.
let mut groups = vec![
Group {
name: "role_people_self_set_password".to_string(),
name: GroupName::RolePeopleSelfSetPassword,
role: ActorRole::PeopleSelfSetPassword,
..Default::default()
},
Group {
name: "role_people_pii_reader".to_string(),
name: GroupName::RolePeoplePiiReader,
role: ActorRole::PeoplePiiReader,
..Default::default()
},
Group {
name: "role_people_self_write_mail".to_string(),
name: GroupName::RolePeopleSelfMailWrite,
role: ActorRole::PeopleSelfMailWrite,
..Default::default()
},
Group {
name: "role_people_self_read_account".to_string(),
name: GroupName::RolePeopleSelfReadProfile,
role: ActorRole::PeopleSelfReadProfile,
..Default::default()
},
Group {
name: "role_people_self_read_memberof".to_string(),
name: GroupName::RolePeopleSelfReadMemberOf,
role: ActorRole::PeopleSelfReadMemberOf,
..Default::default()
},
Group {
name: GroupName::RolePeopleGroupAdmin,
role: ActorRole::PeopleGroupAdmin,
..Default::default()
},
];

// PHASE 3 - generate persons
// - assign them credentials of various types.
let mut persons = Vec::with_capacity(profile.person_count() as usize);
let mut person_usernames = BTreeSet::new();

let model = *profile.model();

for _ in 0..profile.person_count() {
let given_name = given_names
.choose(&mut seeded_rng)
Expand Down Expand Up @@ -122,8 +130,6 @@ pub async fn populate(_client: &KanidmOrcaClient, profile: Profile) -> Result<St

let roles = BTreeSet::new();

let model = Model::Basic;

// Data is ready, make changes to the server. These should be idempotent if possible.
let p = Person {
preflight_state: PreflightState::Present,
Expand All @@ -146,15 +152,26 @@ pub async fn populate(_client: &KanidmOrcaClient, profile: Profile) -> Result<St
// them a baseline of required accounts with some variation. This
// way in each test it's guaranteed that *at least* one person
// to each role always will exist and be operational.
let member_count_by_group: HashMap<GroupName, u64> = profile
.get_properties_by_group()
.iter()
.filter_map(|(name, properties)| {
let group_name = GroupName::try_from(name).ok()?;
properties.member_count.map(|count| (group_name, count))
})
.collect();

for group in groups.iter_mut() {
// For now, our baseline is 20%. We can adjust this in future per
// role for example.
let baseline = persons.len() / 3;
let inverse = persons.len() - baseline;
// Randomly add extra from the inverse
let extra = Uniform::new(0, inverse);
let persons_to_choose = baseline + seeded_rng.sample(extra);
let persons_to_choose = match member_count_by_group.get(&group.name) {
Some(person_count) => *person_count as usize,
None => {
let baseline = persons.len() / 3;
let inverse = persons.len() - baseline;
// Randomly add extra from the inverse
let extra = Uniform::new(0, inverse);
baseline + seeded_rng.sample(extra)
}
};

assert!(persons_to_choose <= persons.len());

Expand Down Expand Up @@ -186,8 +203,10 @@ pub async fn populate(_client: &KanidmOrcaClient, profile: Profile) -> Result<St

// PHASE 7 - given the integrations and groupings,

// Return the state.
drop(member_count_by_group); // it looks ugly but we have to do this to reassure the borrow checker we can return profile, as we were borrowing
//the group names from it

// Return the state.
let state = State {
profile,
// ---------------
Expand Down
7 changes: 4 additions & 3 deletions tools/orca/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
#[macro_use]
extern crate tracing;

use std::path::PathBuf;
use std::process::ExitCode;

use clap::Parser;
use opt::OrcaOpt;

use crate::profile::{Profile, ProfileBuilder};

Expand All @@ -27,14 +27,13 @@ mod generate;
mod kani;
mod model;
mod models;
mod opt;
mod populate;
mod profile;
mod run;
mod state;
mod stats;

include!("./opt.rs");

impl OrcaOpt {
fn debug(&self) -> bool {
match self {
Expand Down Expand Up @@ -77,6 +76,7 @@ fn main() -> ExitCode {
seed,
profile_path,
threads,
model,
} => {
// For now I hardcoded some dimensions, but we should prompt
// the user for these later.
Expand All @@ -96,6 +96,7 @@ fn main() -> ExitCode {
extra_uris,
admin_password,
idm_admin_password,
model,
threads,
)
.seed(seed);
Expand Down
67 changes: 54 additions & 13 deletions tools/orca/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ pub enum ActorRole {
PeopleSelfReadProfile,
PeopleSelfReadMemberOf,
PeopleSelfSetPassword,
PeopleGroupAdmin,
}

impl ActorRole {
Expand All @@ -61,6 +62,7 @@ impl ActorRole {
| ActorRole::PeopleSelfSetPassword => None,
ActorRole::PeoplePiiReader => Some(&["idm_people_pii_read"]),
ActorRole::PeopleSelfMailWrite => Some(&["idm_people_self_mail_write"]),
ActorRole::PeopleGroupAdmin => Some(&["idm_group_admins"]),
}
}
}
Expand All @@ -71,13 +73,13 @@ pub trait ActorModel {
&mut self,
client: &KanidmClient,
person: &Person,
) -> Result<EventRecord, Error>;
) -> Result<Vec<EventRecord>, Error>;
}

pub async fn login(
client: &KanidmClient,
person: &Person,
) -> Result<(TransitionResult, EventRecord), Error> {
) -> Result<(TransitionResult, Vec<EventRecord>), Error> {
// Should we measure the time of each call rather than the time with multiple calls?
let start = Instant::now();
let result = match &person.credential {
Expand All @@ -101,7 +103,7 @@ pub async fn person_set_self_mail(
client: &KanidmClient,
person: &Person,
values: &[&str],
) -> Result<(TransitionResult, EventRecord), Error> {
) -> Result<(TransitionResult, Vec<EventRecord>), Error> {
// Should we measure the time of each call rather than the time with multiple calls?
let person_username = person.username.as_str();

Expand All @@ -121,11 +123,50 @@ pub async fn person_set_self_mail(
Ok(parsed_result)
}

pub async fn person_create_group(
client: &KanidmClient,
group_name: &str,
) -> Result<(TransitionResult, Vec<EventRecord>), Error> {
let start = Instant::now();
let result = client.idm_group_create(group_name, None).await;

let duration = Instant::now().duration_since(start);
let parsed_result = parse_call_result_into_transition_result_and_event_record(
result,
EventDetail::PersonCreateGroup,
start,
duration,
);

Ok(parsed_result)
}

pub async fn person_add_group_members(
client: &KanidmClient,
group_name: &str,
group_members: &[&str],
) -> Result<(TransitionResult, Vec<EventRecord>), Error> {
let start = Instant::now();
let result = client
.idm_group_add_members(group_name, group_members)
.await;

let duration = Instant::now().duration_since(start);
let parsed_result = parse_call_result_into_transition_result_and_event_record(
result,
EventDetail::PersonAddGroupMembers,
start,
duration,
);

Ok(parsed_result)
}

pub async fn person_set_self_password(
client: &KanidmClient,
person: &Person,
pw: &str,
) -> Result<(TransitionResult, EventRecord), Error> {
) -> Result<(TransitionResult, Vec<EventRecord>), Error> {
// Should we measure the time of each call rather than the time with multiple calls?
let person_username = person.username.as_str();

Expand All @@ -148,7 +189,7 @@ pub async fn person_set_self_password(
pub async fn privilege_reauth(
client: &KanidmClient,
person: &Person,
) -> Result<(TransitionResult, EventRecord), Error> {
) -> Result<(TransitionResult, Vec<EventRecord>), Error> {
let start = Instant::now();

let result = match &person.credential {
Expand All @@ -169,7 +210,7 @@ pub async fn privilege_reauth(
pub async fn logout(
client: &KanidmClient,
_person: &Person,
) -> Result<(TransitionResult, EventRecord), Error> {
) -> Result<(TransitionResult, Vec<EventRecord>), Error> {
let start = Instant::now();
let result = client.logout().await;
let duration = Instant::now().duration_since(start);
Expand All @@ -185,7 +226,7 @@ pub async fn logout(
pub async fn person_get_self_account(
client: &KanidmClient,
person: &Person,
) -> Result<(TransitionResult, EventRecord), Error> {
) -> Result<(TransitionResult, Vec<EventRecord>), Error> {
let start = Instant::now();
let result = client.idm_person_account_get(&person.username).await;
let duration = Instant::now().duration_since(start);
Expand All @@ -200,7 +241,7 @@ pub async fn person_get_self_account(
pub async fn person_get_self_memberof(
client: &KanidmClient,
person: &Person,
) -> Result<(TransitionResult, EventRecord), Error> {
) -> Result<(TransitionResult, Vec<EventRecord>), Error> {
let start = Instant::now();
let result = client
.idm_person_account_get_attr(&person.username, "memberof")
Expand All @@ -219,25 +260,25 @@ fn parse_call_result_into_transition_result_and_event_record<T>(
details: EventDetail,
start: Instant,
duration: Duration,
) -> (TransitionResult, EventRecord) {
) -> (TransitionResult, Vec<EventRecord>) {
match result {
Ok(_) => (
TransitionResult::Ok,
EventRecord {
vec![EventRecord {
start,
duration,
details,
},
}],
),
Err(client_err) => {
debug!(?client_err);
(
TransitionResult::Error,
EventRecord {
vec![EventRecord {
start,
duration,
details: EventDetail::Error,
},
}],
)
}
}
Expand Down
2 changes: 1 addition & 1 deletion tools/orca/src/models/auth_only.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ impl ActorModel for ActorAuthOnly {
&mut self,
client: &KanidmClient,
person: &Person,
) -> Result<EventRecord, Error> {
) -> Result<Vec<EventRecord>, Error> {
let transition = self.next_transition();

if let Some(delay) = transition.delay {
Expand Down
Loading

0 comments on commit 60e5b09

Please sign in to comment.