1#![deny(warnings)]
2#![warn(unused_extern_crates)]
3#![allow(clippy::panic)]
4#![deny(clippy::unreachable)]
5#![deny(clippy::await_holding_lock)]
6#![deny(clippy::needless_pass_by_value)]
7#![deny(clippy::trivially_copy_pass_by_ref)]
8
9#[cfg(not(any(target_family = "windows", target_os = "illumos")))]
10#[global_allocator]
11static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
12
13#[macro_use]
14extern crate tracing;
15
16use std::process::ExitCode;
17
18use clap::Parser;
19use opt::OrcaOpt;
20
21use crate::profile::{Profile, ProfileBuilder};
22
23use tokio::{runtime::Runtime, sync::broadcast};
24
25mod error;
26mod generate;
27mod kani;
28mod model;
29mod models;
30mod opt;
31mod populate;
32mod profile;
33mod run;
34mod state;
35mod stats;
36
37impl OrcaOpt {
38 fn debug(&self) -> bool {
39 match self {
40 OrcaOpt::Version { common }
41 | OrcaOpt::SetupWizard { common, .. }
42 | OrcaOpt::TestConnection { common, .. }
43 | OrcaOpt::GenerateData { common, .. }
44 | OrcaOpt::PopulateData { common, .. }
45 | OrcaOpt::Run { common, .. } => common.debug,
46 }
47 }
48}
49
50fn main() -> ExitCode {
51 let opt = OrcaOpt::parse();
52
53 if opt.debug() {
54 ::std::env::set_var(
55 "RUST_LOG",
56 "orca=debug,kanidm=debug,kanidm_client=debug,webauthn=debug",
57 );
58 }
59
60 tracing_subscriber::fmt::init();
61
62 info!("Orca - the Kanidm Load Testing Utility.");
63 debug!("cli -> {:?}", opt);
64 match opt {
65 OrcaOpt::Version { .. } => {
66 println!("orca {}", env!("KANIDM_PKG_VERSION"));
67 ExitCode::SUCCESS
68 }
69
70 OrcaOpt::SetupWizard {
72 common: _,
73 admin_password,
74 idm_admin_password,
75 control_uri,
76 seed,
77 profile_path,
78 threads,
79 model,
80 dump_raw_data,
81 } => {
82 let seed = seed.map(|seed| {
86 if seed < 0 {
87 seed.wrapping_mul(-1) as u64
88 } else {
89 seed as u64
90 }
91 });
92
93 let extra_uris = Vec::with_capacity(0);
94
95 let builder = ProfileBuilder::new(
96 control_uri,
97 extra_uris,
98 admin_password,
99 idm_admin_password,
100 model,
101 threads,
102 dump_raw_data,
103 )
104 .seed(seed);
105
106 let profile = match builder.build() {
107 Ok(p) => p,
108 Err(_err) => {
109 return ExitCode::FAILURE;
110 }
111 };
112
113 match profile.write_to_path(&profile_path) {
114 Ok(_) => ExitCode::SUCCESS,
115 Err(_err) => ExitCode::FAILURE,
116 }
117 }
118
119 OrcaOpt::TestConnection {
121 common: _,
122 profile_path,
123 } => {
124 let profile = match Profile::try_from(profile_path.as_path()) {
125 Ok(p) => p,
126 Err(_err) => {
127 return ExitCode::FAILURE;
128 }
129 };
130
131 info!("Performing conntest of {}", profile.control_uri());
132
133 let runtime = build_tokio_runtime(Some(1));
135 runtime.block_on(async {
136 match kani::KanidmOrcaClient::new(&profile).await {
137 Ok(_) => {
138 info!("success");
139 ExitCode::SUCCESS
140 }
141 Err(_err) => ExitCode::FAILURE,
142 }
143 })
144 }
145
146 OrcaOpt::GenerateData {
148 common: _,
149 profile_path,
150 state_path,
151 } => {
152 let profile = match Profile::try_from(profile_path.as_path()) {
153 Ok(p) => p,
154 Err(_err) => {
155 return ExitCode::FAILURE;
156 }
157 };
158
159 let runtime = build_tokio_runtime(Some(1));
161
162 runtime.block_on(async {
163 let client = match kani::KanidmOrcaClient::new(&profile).await {
164 Ok(client) => client,
165 Err(_err) => {
166 return ExitCode::FAILURE;
167 }
168 };
169
170 let state = match generate::populate(&client, profile).await {
172 Ok(s) => s,
173 Err(_err) => {
174 return ExitCode::FAILURE;
175 }
176 };
177
178 match state.write_to_path(&state_path) {
179 Ok(_) => ExitCode::SUCCESS,
180 Err(_err) => ExitCode::FAILURE,
181 }
182 })
183 }
184
185 OrcaOpt::PopulateData {
187 common: _,
188 state_path,
189 } => {
190 let state = match state::State::try_from(state_path.as_path()) {
191 Ok(p) => p,
192 Err(_err) => {
193 return ExitCode::FAILURE;
194 }
195 };
196
197 let runtime = build_tokio_runtime(state.thread_count);
199
200 runtime.block_on(async {
201 match populate::preflight(state).await {
202 Ok(_) => ExitCode::SUCCESS,
203 Err(_err) => ExitCode::FAILURE,
204 }
205 })
206 }
207
208 OrcaOpt::Run {
210 common: _,
211 state_path,
212 } => {
213 let state = match state::State::try_from(state_path.as_path()) {
214 Ok(p) => p,
215 Err(_err) => {
216 return ExitCode::FAILURE;
217 }
218 };
219 let runtime = build_tokio_runtime(state.thread_count);
222 runtime.block_on(async {
228 let (control_tx, control_rx) = broadcast::channel(8);
229
230 let mut run_execute = tokio::task::spawn(run::execute(state, control_rx));
231
232 loop {
233 tokio::select! {
234 result = &mut run_execute => {
237 match result {
238 Ok(_) => {
239 return ExitCode::SUCCESS;
240 }
241 Err(_err) => {
242 return ExitCode::FAILURE;
243 }
244 };
245 }
246 Ok(()) = tokio::signal::ctrl_c() => {
248 info!("Stopping Task ...");
249 let _ = control_tx.send(run::Signal::Stop);
250 }
251 Some(()) = async move {
252 let sigterm = tokio::signal::unix::SignalKind::terminate();
253 #[allow(clippy::unwrap_used)]
254 tokio::signal::unix::signal(sigterm).unwrap().recv().await
255 } => {
256 return ExitCode::FAILURE;
258 }
259 Some(()) = async move {
260 let sigterm = tokio::signal::unix::SignalKind::alarm();
261 #[allow(clippy::unwrap_used)]
262 tokio::signal::unix::signal(sigterm).unwrap().recv().await
263 } => {
264 }
266 Some(()) = async move {
267 let sigterm = tokio::signal::unix::SignalKind::hangup();
268 #[allow(clippy::unwrap_used)]
269 tokio::signal::unix::signal(sigterm).unwrap().recv().await
270 } => {
271 }
273 Some(()) = async move {
274 let sigterm = tokio::signal::unix::SignalKind::user_defined1();
275 #[allow(clippy::unwrap_used)]
276 tokio::signal::unix::signal(sigterm).unwrap().recv().await
277 } => {
278 }
280 Some(()) = async move {
281 let sigterm = tokio::signal::unix::SignalKind::user_defined2();
282 #[allow(clippy::unwrap_used)]
283 tokio::signal::unix::signal(sigterm).unwrap().recv().await
284 } => {
285 }
287 }
288 }
289 })
290 }
291 }
292}
293
294fn build_tokio_runtime(threads: Option<usize>) -> Runtime {
297 let mut builder = tokio::runtime::Builder::new_multi_thread();
298 match threads {
299 Some(threads) => builder.worker_threads(threads),
300 None => &mut builder,
301 }
302 .enable_all()
303 .build()
304 .expect("Failed to build tokio runtime")
305}