tag1consulting / goose

Load testing framework, inspired by Locust
https://tag1.com/goose
Apache License 2.0
737 stars 67 forks source link

Caching auth between users #594

Closed buck06191 closed 1 month ago

buck06191 commented 1 month ago

What

Is there a way to cache authentication between users? I'm using Goose to load test a REST endpoint and I'm currently using .start_stop() to make the auth request on a per-user basis, but all of the users are then having to hit an endpoint to auth individually.

If I scale up to a spike of say 2000 users, then that's a sudden increase in the number of auth requests being made and I'm worried it might lead to me accidentally load testing the auth service rather than the one we're actually putting under load.

Would the solution be to try use some sort of global variable and then access it from there? Or is there anything in Goose that allows caching auth etc., by scenario rather than by user?

jeremyandrews commented 1 month ago

To only log each user in once, you'd use set_on_start() and the GooseUser will log in and then make all subsequent requests as an authenticated user: https://docs.rs/goose/latest/goose/goose/struct.Transaction.html#method.set_on_start

To truly only log in once, you could instead call test_start(): https://docs.rs/goose/latest/goose/struct.GooseAttack.html#method.test_start

However, you'd then be responsible for saving whatever session information is needed, most likely requiring that you build each user setting the appropriate header or cookie etc: https://docs.rs/goose/latest/goose/goose/struct.GooseUser.html#method.set_client_builder

buck06191 commented 1 month ago

That's really helpful. What's the idiomatic way to pass session information from that global level down into a GooseUser? The user created for the test_start method wouldn't have its information saved and I can't see a way to get information out of that thread and back up to the main function to be passed down to future users?

jeremyandrews commented 1 month ago

When I did something similar for a client some time ago, I used OnceCell. I essentially populated it during test_start() which runs before the GooseUser's are created for the load test, and then used it in set_on_start() to simulate per GooseUser login.

buck06191 commented 1 month ago

Grand. Thank you. I ended up leaning into std::sync::OnceLock. I'll share a very basic snippet of it here for anyone else that comes across this

// globals.rs 
pub static SESSION: OnceLock<Box<Session>> = OnceLock::new();

pub fn build_global_session(
    auth_token: SecretString,
    api_key: SecretString,
) {
    let session = Session {
        auth_token,
        api_key,
    };
    let boxed_session = Box::new(session);

    SESSION.get_or_init(|| boxed_session);
}

// authorisation.rs
pub async fn setup_custom_client(user: &mut GooseUser) -> TransactionResult {
    let session = SESSION.get().expect("Couldn't get the global session data");
    user.set_session_data(session);

    Ok(())
}

Thanks for your help!

jeremyandrews commented 1 month ago

Great! If you'd like to make a quick PR, it would be great to add another example that demonstrates this: https://github.com/tag1consulting/goose/tree/main/examples