-
Notifications
You must be signed in to change notification settings - Fork 54
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix the c8y custom operation status handling #1641
Fix the c8y custom operation status handling #1641
Conversation
A custom operation managed in /etc/tedge/operations/c8y could be marked successful unconditionally and the status had been sent out before the operation has completed. To fix, now the Cumulocity converter holds a mqtt publisher and sends executing and successful/failed message from the converter. Either successful or failed is determined by the exit code (0 or not). Also, stdout is reported when successful, and stderr for a failed case. Signed-off-by: Rina Fujino <18257209+rina23q@users.noreply.github.com>
Reduce the arguments from 9 to 7 by introducing a new struct to keep c8y device information. Signed-off-by: Rina Fujino <18257209+rina23q@users.noreply.github.com>
a92422d
to
b02e6a8
Compare
[Solved] |
match output.status.code() { | ||
Some(0) => { | ||
let stdout = String::from_utf8(output.stdout).unwrap_or_default(); | ||
let successful_str = format!("503,{op_name},\"{stdout}\""); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this useful to send the whole output of the command to the cloud?
If yes, one needs to escape any character misleading for CSV (notably the quotes "
) and to conform to what is supported by Cumulocity (are multiline stdout supported?).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed with @reubenmiller, what to report to the cloud and how the message should look.
For successful case, sending the stdout
contents as much as possible (within the size limit of c8y) with some sanitising. The detailed, you can find from #1603 "Result/failure message parsing rules".
My commit e76e172 changed this part as described in the original ticket.
} | ||
_ => { | ||
let stderr = String::from_utf8(output.stderr).unwrap_or_default(); | ||
let failed_str = format!("502,{op_name},\"{stderr}\""); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One needs to escape any character misleading for CSV (notably the quotes "
) and to conform to what is supported by Cumulocity (are multiline stderr supported?).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed with @reubenmiller, what to report to the cloud and how the message should look.
For error case, sending the last line of stdout
as failure reason. The detailed, you can find from #1603 "Result/failure message parsing rules".
My commit e76e172 changed this part as described in the original ticket.
let topic = C8yTopic::SmartRestResponse.to_topic().unwrap(); | ||
let executing_str = format!("501,{op_name}"); | ||
mqtt_publisher | ||
.send(Message::new(&topic, executing_str.as_str())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although I agree that sending this executing status here before the actual command itself is executed(which could take time to complete), provides better UX from the cloud, it is done at the cost of breaking the "converter" abstraction of it "just being a message converter without any MQTT business".
Since this is just a short term hack anyway, I was wondering why not keep it simple like this:
- Run the command and wait for the exit code.
- If exit code is 0, return both EXECUTING and SUCCESSFUL messages in the response
Vec<Message>
- If exit code is 1, return both EXECUTING and FAILED messages in the response
Vec<Message>
.
I understand that the executing response will be delayed for long running operations and results in suboptimal UX. So, we need to choose between a "complex hack for better UX" for vs "simple hack for suboptimal UX".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On a second thought, I feel that your current approach is closer to the actor model that we are going to, where the C8yMapperActor
would receive the conversion response from the C8yConvertorActor
over a channel, like the mqtt_publisher
that you're sharing between the mapper
and the converter
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To sanitize an utf8 string I would work at the char level and not to byte level.
Something along the lines:
input.chars().filter( ...).map(...).take(max).collect()
There are then 2 tricky points:
- How to sanitize a non-utf8 input? One can use the current code.
- How to cut the utf8 output to fit the maximum size that is a byte count? I would try to use an
iter::scan
that accumulates the number of bytes used so far.
input
.chars()
.filter(...)
.flat_map(...)
.scan(0, |bytes_count, char| {
*bytes_count = *bytes_count + number_bytes(char);
Some( (*bytes_count, char))
})
.take_while(|(size, _)| { size <= max })
.map(|(_,char)| {char})
.collect();
Signed-off-by: Rina Fujino <18257209+rina23q@users.noreply.github.com>
The output of command execution cannot be sent as it is, because - it might contain unacceptable characters. - double quotes need to be escaped. - the total message size might be too big. Also, the failure reason should be brief, therefore, the sending the whole output of stderr is not appropriate. This commit adds sanitizing functions and take only the last line for error case to report. Signed-off-by: Rina Fujino <18257209+rina23q@users.noreply.github.com>
0ac32b9
to
7e79c47
Compare
Thanks @didier-wenzek. You are absolutely right. Now all sanitising functions were re-written. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approved - with two comments.
/// Sanitize the input to be SmartREST compatible. | ||
/// If the input contains invalid UTF-8, it returns an empty String. | ||
/// - Remove all control characters except for `\n`, `\t`, `\r`. | ||
/// - Double quote is escaped as `\"`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// - Double quote is escaped as `\"`. | |
/// - Double quote is escaped as `\"\"`. |
.collect::<String>() | ||
.replace('"', "\"\"") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This intermediate collect
could be avoid by using a flat_map
instead of the replace
.
Signed-off-by: Rina Fujino <18257209+rina23q@users.noreply.github.com>
0b3713c
to
1245231
Compare
Proposed changes
A custom operation managed in
/etc/tedge/operations/c8y
could be marked successful unconditionally and the status had been sent out before the operation has completed.To fix, now the Cumulocity converter holds a mqtt publisher and sends executing and successful/failed message from the converter. Either successful or failed is determined by the exit code (0 or not).
Also, stdout is reported when successful, and stderr for a failed case.
Types of changes
Paste Link to the issue
#1603
Checklist
cargo fmt
as mentioned in CODING_GUIDELINEScargo clippy
as mentioned in CODING_GUIDELINESFurther comments