Skip to content

Commit

Permalink
Merge pull request #473 from jeremyandrews/controller-testplan
Browse files Browse the repository at this point in the history
add support for configuring test-plan through the controller
  • Loading branch information
slashrsm authored May 11, 2022
2 parents fad7836 + 230c161 commit 3ced8bd
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 0.16.1-dev
- [#464](https://github.com/tag1consulting/goose/pull/464) add `startuptime` (and `startup_time`) TIME to controllers, setting how long the load test should spend starting configured number of users
- [#469](https://github.com/tag1consulting/goose/pull/469) support `users` INT command on controllers during a running load test
- [#473](https://github.com/tag1consulting/goose/pull/473) introduce `test-plan PLAN` command allowing configuration of test plan with the controller during running and idle load tests

## 0.16.0 May 1, 2022
- [#431](https://github.com/tag1consulting/goose/pull/431) rename `--no-granular-data` to `--no-granular-report`
Expand Down
114 changes: 112 additions & 2 deletions src/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ use tokio_tungstenite::tungstenite::Message;
/// the necessary logic to `ControllerCommand::validate_value`.
/// 3. Add any necessary parent process logic for the command to
/// `GooseAttack::handle_controller_requests` (also in this file).
/// 4. Add a test for the new command in tests/controller.rs.
#[derive(Clone, Debug, EnumIter, PartialEq)]
pub enum ControllerCommand {
/// Displays a list of all commands supported by the Controller.
Expand Down Expand Up @@ -142,6 +143,17 @@ pub enum ControllerCommand {
///
/// This can be configured when Goose is idle as well as when a Goose load test is running.
RunTime,
/// Define a load test plan. This will replace the previously configured test plan, if any.
///
/// # Example
/// Tells Goose to launch 10 users in 5 seconds, maintain them for 30 seconds, and then spend 5 seconds
/// stopping them.
/// ```notest
/// testplan "10,5s;10,30s;0,5s"
/// ```
///
/// Can be configured on an idle or running load test.
TestPlan,
/// Display the current [`GooseConfiguration`](../struct.GooseConfiguration.html)s.
///
/// # Example
Expand Down Expand Up @@ -183,6 +195,8 @@ pub enum ControllerCommand {
}

/// Defines details around identifying and processing ControllerCommands.
///
/// As order doesn't matter here, it's preferred to define commands in alphabetical order.
impl ControllerCommand {
/// Each ControllerCommand must define complete ControllerCommentDetails.
/// - `help` returns a ControllerHelp struct that is used to provide inline help.
Expand Down Expand Up @@ -261,7 +275,7 @@ impl ControllerCommand {
ControllerCommand::Host => ControllerCommandDetails {
help: ControllerHelp {
name: "host HOST",
description: "set host to load test, ie https://web.site/\n",
description: "set host to load test, (ie https://web.site/)\n",
},
regex: r"(?i)^(host|hostname|host_name|host-name) ((https?)://.+)$",
process_response: Box::new(|response| {
Expand Down Expand Up @@ -306,7 +320,7 @@ impl ControllerCommand {
ControllerCommand::RunTime => ControllerCommandDetails {
help: ControllerHelp {
name: "runtime TIME",
description: "set how long to run test, ie 1h30m5s\n\n",
description: "set how long to run test, (ie 1h30m5s)\n",
},
regex: r"(?i)^(run|runtime|run_time|run-time|) (\d+|((\d+?)h)?((\d+?)m)?((\d+?)s)?)$",
process_response: Box::new(|response| {
Expand Down Expand Up @@ -378,6 +392,20 @@ impl ControllerCommand {
}
}),
},
ControllerCommand::TestPlan => ControllerCommandDetails {
help: ControllerHelp {
name: "test-plan PLAN",
description: "define or replace test-plan, (ie 10,5m;10,1h;0,30s)\n\n",
},
regex: r"(?i)^(testplan|test_plan|test-plan|plan) (((\d+)\s*,\s*(\d+|((\d+?)h)?((\d+?)m)?((\d+?)s)?)*;*)+)$",
process_response: Box::new(|response| {
if let ControllerResponseMessage::Bool(true) = response {
Ok("test-plan configured".to_string())
} else {
Err("failed to configure test-plan, be sure test-plan is valid".to_string())
}
}),
},
ControllerCommand::Users => ControllerCommandDetails {
help: ControllerHelp {
name: "users INT",
Expand Down Expand Up @@ -467,6 +495,7 @@ impl GooseAttack {
"request from controller client {}: {:?}",
message.client_id, message.request
);
// As order is not important here, commands should be defined in alphabetical order.
match &message.request.command {
// Send back a copy of the running configuration.
ControllerCommand::Config | ControllerCommand::ConfigJson => {
Expand Down Expand Up @@ -605,6 +634,9 @@ impl GooseAttack {
// Use expect() as Controller uses regex to validate this is an integer.
let new_users = usize::from_str(users)
.expect("failed to convert string to usize");
// If setting users, any existing configuration for a test plan isn't valid.
self.configuration.test_plan = None;

match self.attack_phase {
// If the load test is idle, simply update the configuration.
AttackPhase::Idle => {
Expand Down Expand Up @@ -717,6 +749,8 @@ impl GooseAttack {
self.configuration.hatch_rate, hatch_rate
);
self.configuration.hatch_rate = Some(hatch_rate.clone());
// If setting hatch_rate, any existing configuration for a test plan isn't valid.
self.configuration.test_plan = None;
self.reply_to_controller(
message,
ControllerResponseMessage::Bool(true),
Expand Down Expand Up @@ -744,6 +778,8 @@ impl GooseAttack {
self.configuration.startup_time, startup_time
);
self.configuration.startup_time = startup_time.clone();
// If setting startup_time, any existing configuration for a test plan isn't valid.
self.configuration.test_plan = None;
self.reply_to_controller(
message,
ControllerResponseMessage::Bool(true),
Expand Down Expand Up @@ -775,6 +811,8 @@ impl GooseAttack {
self.configuration.run_time, run_time
);
self.configuration.run_time = run_time.clone();
// If setting run_time, any existing configuration for a test plan isn't valid.
self.configuration.test_plan = None;
self.reply_to_controller(
message,
ControllerResponseMessage::Bool(true),
Expand All @@ -786,6 +824,78 @@ impl GooseAttack {
);
}
}
ControllerCommand::TestPlan => {
if let Some(value) = &message.request.value {
match value.parse::<TestPlan>() {
Ok(t) => {
// Switch the configuration to use the test plan.
self.configuration.test_plan = Some(t.clone());
self.configuration.users = None;
self.configuration.hatch_rate = None;
self.configuration.startup_time = "0".to_string();
self.configuration.run_time = "0".to_string();
match self.attack_phase {
// If the load test is idle, just update the configuration.
AttackPhase::Idle => {
self.reply_to_controller(
message,
ControllerResponseMessage::Bool(true),
);
}
// If the load test is running, rebuild the active test plan.
AttackPhase::Increase
| AttackPhase::Decrease
| AttackPhase::Maintain => {
// Rebuild the active test plan.
self.test_plan = t;

// Reallocate users.
self.weighted_users = self.weight_scenario_users(
self.test_plan.total_users(),
)?;

// Determine how long the current step has been running.
let elapsed = self.step_elapsed() as usize;

// Insert the current state of the test plan before the new test plan.
self.test_plan.steps.insert(
0,
(goose_attack_run_state.active_users, elapsed),
);

// Finally, advance to the next step to adjust user count.
self.advance_test_plan(goose_attack_run_state);

// The load test is successfully reconfigured.
self.reply_to_controller(
message,
ControllerResponseMessage::Bool(true),
);
}
_ => {
unreachable!("Controller used in impossible phase.")
}
}
}
Err(e) => {
warn!("Controller provided invalid test_plan: {}", e);
self.reply_to_controller(
message,
ControllerResponseMessage::Bool(false),
);
}
}
} else {
warn!(
"Controller didn't provide test_plan: {:#?}",
&message.request
);
self.reply_to_controller(
message,
ControllerResponseMessage::Bool(false),
);
}
}
// These messages shouldn't be received here.
ControllerCommand::Help | ControllerCommand::Exit => {
warn!("Unexpected command: {:?}", &message.request);
Expand Down
5 changes: 3 additions & 2 deletions src/docs/goose-book/src/controller/telnet.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ start start an idle load test
stop stop a running load test and return to idle state
shutdown shutdown load test and exit controller

host HOST set host to load test, ie https://web.site/
host HOST set host to load test, (ie https://web.site/)
hatchrate FLOAT set per-second rate users hatch
startup-time TIME set total time to take starting users
users INT set number of simulated users
runtime TIME set how long to run test, ie 1h30m5s
runtime TIME set how long to run test, (ie 1h30m5s)
test-plan PLAN define or replace test-plan, (ie 10,5m;10,1h;0,30s)

config display load test configuration
config-json display load test configuration in json format
Expand Down
23 changes: 23 additions & 0 deletions tests/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,28 @@ async fn run_standalone_test(test_type: TestType) {
}
}
}
ControllerCommand::TestPlan => {
match test_state.step {
// Try and set an invalid testplan.
0 => {
make_request(&mut test_state, "testplan 10\r\n");
}
1 => {
// Confirm that an invalid testplan fails.
assert!(response.starts_with("unrecognized command"));

// Set a valid test plan.
make_request(&mut test_state, "test_plan 10,2s;10,30m5s;0,1h2s\r\n");
}
_ => {
// Confirm that an invalid testplan fails.
assert!(response.starts_with("test-plan configured"));

// Move onto the next command.
test_state = update_state(Some(test_state), &test_type);
}
}
}
ControllerCommand::Users => {
match test_state.step {
// Reconfigure the number of users simulated by the load test.
Expand Down Expand Up @@ -669,6 +691,7 @@ fn update_state(test_state: Option<TestState>, test_type: &TestType) -> TestStat
ControllerCommand::Exit,
ControllerCommand::Help,
ControllerCommand::Host,
ControllerCommand::TestPlan,
ControllerCommand::Users,
ControllerCommand::HatchRate,
ControllerCommand::StartupTime,
Expand Down

0 comments on commit 3ced8bd

Please sign in to comment.