Skip to content

Commit

Permalink
Merge pull request #2853 from didier-wenzek/feat/sub-sub-workflow
Browse files Browse the repository at this point in the history
feat: add support for sub-sub operations
  • Loading branch information
didier-wenzek authored May 6, 2024
2 parents c85c4ae + 33e6890 commit d05e9b6
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 6 deletions.
29 changes: 25 additions & 4 deletions crates/core/tedge_api/src/workflow/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,10 +298,9 @@ impl GenericCommandState {
///
/// Return None if the given id is not a sub command identifier, i.e. if not generated with [sub_command_id].
fn extract_invoking_command_id(sub_cmd_id: &str) -> Option<(&str, &str)> {
match sub_cmd_id.split(':').collect::<Vec<&str>>()[..] {
["sub", operation, cmd_id, ..] => Some((operation, cmd_id)),
_ => None,
}
sub_cmd_id
.strip_prefix("sub:")
.and_then(|op_id| op_id.split_once(':'))
}

fn target(&self) -> Option<String> {
Expand Down Expand Up @@ -619,6 +618,28 @@ mod tests {
);
}

#[test]
fn retrieve_invoking_command_of_sub_sub_command() {
let topic =
Topic::new_unchecked("te/device/main///cmd/child/sub:parent:sub:grand-parent:456");
let payload = r#"{ "status":"init" }"#;
let command = mqtt_channel::MqttMessage::new(&topic, payload);
let cmd = GenericCommandState::from_command_message(&command).expect("parsing error");
assert_eq!(
cmd,
GenericCommandState {
topic: topic.clone(),
status: "init".to_string(),
payload: json!({
"status": "init"
}),
invoking_command_topic: Some(
"te/device/main///cmd/parent/sub:grand-parent:456".to_string()
),
}
);
}

#[test]
fn inject_json_into_parameters() {
let topic = Topic::new_unchecked("te/device/main///cmd/make_it/123");
Expand Down
83 changes: 83 additions & 0 deletions crates/core/tedge_api/src/workflow/supervisor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,3 +282,86 @@ impl CommandBoard {
self.commands.remove(topic_name);
}
}

#[cfg(test)]
mod tests {
use super::*;
use mqtt_channel::Topic;

#[test]
fn retrieve_invoking_command_hierarchy() {
let mut workflows = WorkflowSupervisor::default();

let level_1_op = OperationType::Custom("level_1".to_string());
let level_2_op = OperationType::Custom("level_2".to_string());
let level_3_op = OperationType::Custom("level_3".to_string());

workflows
.register_builtin_workflow(level_1_op.clone())
.unwrap();
workflows
.register_builtin_workflow(level_2_op.clone())
.unwrap();
workflows
.register_builtin_workflow(level_3_op.clone())
.unwrap();

// Start a level_1 operation
let level_1_cmd = GenericCommandState::from_command_message(&MqttMessage::new(
&Topic::new_unchecked("te/device/foo///cmd/level_1/id_1"),
r#"{ "status":"init" }"#,
))
.unwrap();
workflows
.apply_external_update(&level_1_op, level_1_cmd.clone())
.unwrap();

// A level 1 command has no invoking command nor root invoking command
assert!(workflows.invoking_command_state(&level_1_cmd).is_none());
assert!(workflows
.root_invoking_command_state(&level_1_cmd)
.is_none());

// Start a level_2 operation, sub-command of the previous level_1 command
let level_2_cmd = GenericCommandState::from_command_message(&MqttMessage::new(
&Topic::new_unchecked("te/device/foo///cmd/level_2/sub:level_1:id_1"),
r#"{ "status":"init" }"#,
))
.unwrap();
workflows
.apply_external_update(&level_2_op, level_2_cmd.clone())
.unwrap();

// The invoking command of the level_2 command, is the previous level_1 command
// The later is also the root invoking command
assert_eq!(
workflows.invoking_command_state(&level_2_cmd),
Some(&level_1_cmd)
);
assert_eq!(
workflows.root_invoking_command_state(&level_2_cmd),
Some(&level_1_cmd)
);

// Start a level_3 operation, sub-command of the previous level_2 command
let level_3_cmd = GenericCommandState::from_command_message(&MqttMessage::new(
&Topic::new_unchecked("te/device/foo///cmd/level_3/sub:level_2:sub:level_1:id_1"),
r#"{ "status":"init" }"#,
))
.unwrap();
workflows
.apply_external_update(&level_3_op, level_3_cmd.clone())
.unwrap();

// The invoking command of the level_3 command, is the previous level_2 command
// The root invoking command of the level_3 command, is the original level_1 command
assert_eq!(
workflows.invoking_command_state(&level_3_cmd),
Some(&level_2_cmd)
);
assert_eq!(
workflows.root_invoking_command_state(&level_2_cmd),
Some(&level_1_cmd)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ Invoke sub-operation from a super-command operation
${actual_log} Execute Command cat /tmp/test-42.json
${expected_log} Get File ${CURDIR}/super-command-expected.log
Should Be Equal ${actual_log} ${expected_log}
# Remove all dates from the workflow log
${workflow_log}= Execute Command cat /var/log/tedge/agent/workflow-super_command-test-42.log
Should Contain ${workflow_log} item=super_command/test-42 status=init
Should Contain ${workflow_log} item=super_command/test-42 status=executing
Expand All @@ -90,6 +89,14 @@ Use scripts to create sub-operation init states
${expected_log} Get File ${CURDIR}/lite_device_profile.expected.log
Should Be Equal ${actual_log} ${expected_log}

Invoke sub-operation from a sub-operation
Execute Command tedge mqtt pub --retain te/device/main///cmd/gp_command/test-sub-sub '{"status":"init", "output_file":"/tmp/test-sub-sub.json"}'
${cmd_messages} Should Have MQTT Messages te/device/main///cmd/gp_command/test-sub-sub message_pattern=.*successful.* maximum=1
Execute Command tedge mqtt pub --retain te/device/main///cmd/gp_command/test-sub-sub ''
${actual_log} Execute Command cat /tmp/test-sub-sub.json
${expected_log} Get File ${CURDIR}/gp-command-expected.log
Should Be Equal ${actual_log} ${expected_log}

*** Keywords ***

Custom Test Setup
Expand All @@ -113,8 +120,9 @@ Copy Configuration Files
ThinEdgeIO.Transfer To Device ${CURDIR}/restart-tedge-agent.toml /etc/tedge/operations/
ThinEdgeIO.Transfer To Device ${CURDIR}/tedge-agent-pid.sh /etc/tedge/operations/
ThinEdgeIO.Transfer To Device ${CURDIR}/native-reboot.toml /etc/tedge/operations/
ThinEdgeIO.Transfer To Device ${CURDIR}/gp_command.toml /etc/tedge/operations/
ThinEdgeIO.Transfer To Device ${CURDIR}/super_command.toml /etc/tedge/operations/
ThinEdgeIO.Transfer To Device ${CURDIR}/inner_command.toml /etc/tedge/operations/
ThinEdgeIO.Transfer To Device ${CURDIR}/sub_command.toml /etc/tedge/operations/
ThinEdgeIO.Transfer To Device ${CURDIR}/echo-as-json.sh /etc/tedge/operations/
ThinEdgeIO.Transfer To Device ${CURDIR}/write-file.sh /etc/tedge/operations/
ThinEdgeIO.Transfer To Device ${CURDIR}/restart_sub_command.toml /etc/tedge/operations/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{"payload":{"output_file":"/tmp/test-sub-sub.json","status":"dump_payload","x_ter":"some x value","y":"some y value","y_ter":"some y value"},"topic":"te/device/main///cmd/super_command/sub:gp_command:test-sub-sub"}
{"payload":{"output_file":"/tmp/test-sub-sub.json","status":"dump_payload","x":"some x value","y":"some y value"},"topic":"te/device/main///cmd/gp_command/test-sub-sub"}
18 changes: 18 additions & 0 deletions tests/RobotFramework/tests/tedge_agent/workflows/gp_command.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Example of an operation with 3 levels
# gp_command -> super_command -> sub_command
operation = "gp_command"

[init]
operation = "super_command"
input.output_file = "${.payload.output_file}"
on_exec = "awaiting_completion"

[awaiting_completion]
action = "await-operation-completion"
on_success = "dump_payload"
output.x = "${.payload.x_ter}"
output.y = "${.payload.y_ter}"

[dump_payload]
script = "/etc/tedge/operations/write-file.sh ${.payload.output_file} ${.}"
on_success = "successful"

0 comments on commit d05e9b6

Please sign in to comment.