Merge pull request 'add blocking shell cmd execution' (#20) from shell-cmd-executor into main
Reviewed-on: #20 Reviewed-by: lkoester <st167799@stud.uni-stuttgart.de>
This commit is contained in:
commit
4cb9124ab7
58
Cargo.lock
generated
58
Cargo.lock
generated
@ -113,6 +113,12 @@ version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.16.0"
|
||||
@ -295,6 +301,22 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984"
|
||||
|
||||
[[package]]
|
||||
name = "fern"
|
||||
version = "0.6.2"
|
||||
@ -495,6 +517,12 @@ version = "0.2.153"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.21"
|
||||
@ -540,7 +568,7 @@ version = "0.26.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 1.3.2",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"memoffset",
|
||||
@ -620,6 +648,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"socket2",
|
||||
"strum",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"toml",
|
||||
]
|
||||
@ -688,7 +717,7 @@ version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -720,6 +749,19 @@ version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.15"
|
||||
@ -937,6 +979,18 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.59"
|
||||
|
@ -34,6 +34,7 @@ version = ">=0.1.2, <0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.11"
|
||||
tempfile = "3"
|
||||
|
||||
# I don't think we need insane performance. If anything, a small binary is easier to upload
|
||||
# to the satellite.
|
||||
|
@ -171,15 +171,16 @@ def pack_pus_telecommands(q: DefaultPusQueueHelper, cmd_path: str):
|
||||
assert len(cmd_path_list) >= 3
|
||||
data = bytearray()
|
||||
if cmd_path_list[2] == "downlink_logs":
|
||||
data.extend(make_action_cmd_header(UniqueId.Controller, 2))
|
||||
data.extend(make_action_cmd_header(UniqueId.Controller, 2))
|
||||
if cmd_path_list[2] == "downlink_last_img":
|
||||
data.extend(make_action_cmd_header(UniqueId.Controller, 3))
|
||||
data.extend(make_action_cmd_header(UniqueId.Controller, 3))
|
||||
return q.add_pus_tc(
|
||||
PusTelecommand(
|
||||
service=8, subservice=128, apid=EXPERIMENT_APID, app_data=data
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def handle_set_mode_cmd(
|
||||
q: DefaultPusQueueHelper, target_str: str, mode_str: str, apid: int, unique_id: int
|
||||
):
|
||||
|
@ -40,6 +40,7 @@ pub enum GroupId {
|
||||
Hk = 1,
|
||||
Mode = 2,
|
||||
Action = 3,
|
||||
Controller = 4,
|
||||
}
|
||||
|
||||
pub const TEST_EVENT: EventU32TypedSev<SeverityInfo> =
|
||||
@ -214,6 +215,27 @@ pub mod mode_err {
|
||||
pub const WRONG_MODE: ResultU16 = ResultU16::new(GroupId::Mode as u8, 0);
|
||||
}
|
||||
|
||||
pub mod ctrl_err {
|
||||
use super::*;
|
||||
use satrs::res_code::ResultU16;
|
||||
|
||||
#[resultcode]
|
||||
pub const INVALID_CMD_FORMAT: ResultU16 = ResultU16::new(GroupId::Controller as u8, 0);
|
||||
#[resultcode]
|
||||
pub const SHELL_CMD_IO_ERROR: ResultU16 = ResultU16::new(GroupId::Controller as u8, 1);
|
||||
#[resultcode]
|
||||
pub const SHELL_CMD_EXECUTION_FAILURE: ResultU16 = ResultU16::new(GroupId::Controller as u8, 2);
|
||||
#[resultcode]
|
||||
pub const SHELL_CMD_INVALID_FORMAT: ResultU16 = ResultU16::new(GroupId::Controller as u8, 3);
|
||||
|
||||
pub const CTRL_ERR_RESULTS: &[ResultU16Info] = &[
|
||||
INVALID_CMD_FORMAT_EXT,
|
||||
SHELL_CMD_IO_ERROR_EXT,
|
||||
SHELL_CMD_EXECUTION_FAILURE_EXT,
|
||||
SHELL_CMD_INVALID_FORMAT_EXT,
|
||||
];
|
||||
}
|
||||
|
||||
pub mod pool {
|
||||
use satrs::pool::{StaticMemoryPool, StaticPoolConfig};
|
||||
|
||||
@ -295,7 +317,7 @@ pub mod tasks {
|
||||
}
|
||||
|
||||
pub fn create_low_priority_ground_dir() {
|
||||
log::info!("Creating low priority to ground directory");
|
||||
log::info!("creating low priority to ground directory");
|
||||
if !Path::new(TO_GROUND_LP_FOLDER_EXPERIMENT).exists()
|
||||
&& std::fs::create_dir_all(TO_GROUND_LP_FOLDER_EXPERIMENT).is_err()
|
||||
{
|
||||
|
@ -7,23 +7,38 @@ use ops_sat_rs::config::{
|
||||
use satrs::action::ActionRequestVariant;
|
||||
use satrs::{
|
||||
action::ActionRequest,
|
||||
params::Params,
|
||||
pus::action::{ActionReplyPus, ActionReplyVariant},
|
||||
request::{GenericMessage, MessageMetadata},
|
||||
res_code::ResultU16,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
env::temp_dir,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
sync::{atomic::AtomicBool, mpsc, Arc},
|
||||
};
|
||||
|
||||
use ops_sat_rs::config::ctrl_err::{
|
||||
SHELL_CMD_EXECUTION_FAILURE, SHELL_CMD_INVALID_FORMAT, SHELL_CMD_IO_ERROR,
|
||||
};
|
||||
|
||||
use crate::requests::CompositeRequest;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ShellCmd<'a> {
|
||||
cmd: &'a str,
|
||||
args: Vec<&'a str>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
|
||||
#[repr(u32)]
|
||||
pub enum ActionId {
|
||||
StopExperiment = 1,
|
||||
DownlinkLogfile = 2,
|
||||
DownlinkImages = 3,
|
||||
ExecuteShellCommandBlocking = 4,
|
||||
}
|
||||
|
||||
pub struct ExperimentController {
|
||||
@ -76,53 +91,31 @@ impl ExperimentController {
|
||||
}
|
||||
|
||||
pub fn handle_action_request(&mut self, requestor: MessageMetadata, action_req: ActionRequest) {
|
||||
let action_id = ActionId::try_from(action_req.action_id);
|
||||
if action_id.is_err() {
|
||||
let send_completion_failure = |error_code: ResultU16, params: Option<Params>| {
|
||||
let result = self.action_reply_tx.send(GenericMessage::new_action_reply(
|
||||
requestor,
|
||||
action_req.action_id,
|
||||
ActionReplyVariant::CompletionFailed {
|
||||
error_code: INVALID_ACTION_ID,
|
||||
params: None,
|
||||
},
|
||||
ActionReplyVariant::CompletionFailed { error_code, params },
|
||||
));
|
||||
if result.is_err() {
|
||||
log::error!("sending action reply failed");
|
||||
}
|
||||
};
|
||||
let action_id = ActionId::try_from(action_req.action_id);
|
||||
if action_id.is_err() {
|
||||
send_completion_failure(INVALID_ACTION_ID, None);
|
||||
return;
|
||||
}
|
||||
let action_id = action_id.unwrap();
|
||||
match action_id {
|
||||
match action_id.unwrap() {
|
||||
ActionId::StopExperiment => {
|
||||
self.stop_signal
|
||||
.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
let result = self.action_reply_tx.send(GenericMessage::new_action_reply(
|
||||
requestor,
|
||||
action_req.action_id,
|
||||
ActionReplyVariant::Completed,
|
||||
));
|
||||
if result.is_err() {
|
||||
log::error!("Sending action reply failed");
|
||||
}
|
||||
self.send_completion_success(&requestor, &action_req);
|
||||
}
|
||||
ActionId::DownlinkLogfile => {
|
||||
log::info!("Copying logfile into downlink folder");
|
||||
if let Some(logfile_path) = LOGFILE_PATH.get() {
|
||||
if let Ok(logfile_path) = <PathBuf as Clone>::clone(logfile_path)
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
{
|
||||
if std::fs::copy(logfile_path.as_str(), TO_GROUND_FOLDER_EXPERIMENT)
|
||||
.is_err()
|
||||
{
|
||||
log::error!("Copying logfile into downlink path failed")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::error!("Downlink path emtpy")
|
||||
}
|
||||
ActionId::ExecuteShellCommandBlocking => {
|
||||
self.handle_shell_command_execution(&requestor, &action_req);
|
||||
}
|
||||
|
||||
ActionId::DownlinkLogfile => self.handle_downlink_logfile(&requestor, &action_req),
|
||||
// downlink images, default will be the last image, otherwise specified counting down (2 = second to last image, etc.)
|
||||
ActionId::DownlinkImages => {
|
||||
log::info!("Copying images into low priority downlink folder");
|
||||
@ -146,6 +139,104 @@ impl ExperimentController {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_downlink_logfile(&self, requestor: &MessageMetadata, action_req: &ActionRequest) {
|
||||
log::info!("copying logfile into downlink folder");
|
||||
if let Some(logfile_path) = LOGFILE_PATH.get() {
|
||||
if let Ok(logfile_path) = <PathBuf as Clone>::clone(logfile_path)
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
{
|
||||
if std::fs::copy(logfile_path.as_str(), TO_GROUND_FOLDER_EXPERIMENT).is_err() {
|
||||
log::warn!("copying logfile into downlink path failed")
|
||||
}
|
||||
self.send_completion_success(requestor, action_req)
|
||||
}
|
||||
} else {
|
||||
log::warn!("downlink path emtpy")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_completion_success(&self, requestor: &MessageMetadata, action_req: &ActionRequest) {
|
||||
let result = self.action_reply_tx.send(GenericMessage::new_action_reply(
|
||||
*requestor,
|
||||
action_req.action_id,
|
||||
ActionReplyVariant::Completed,
|
||||
));
|
||||
if result.is_err() {
|
||||
log::error!("sending action reply failed");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_completion_failure(
|
||||
&self,
|
||||
requestor: &MessageMetadata,
|
||||
action_req: &ActionRequest,
|
||||
error_code: ResultU16,
|
||||
params: Option<Params>,
|
||||
) {
|
||||
let result = self.action_reply_tx.send(GenericMessage::new_action_reply(
|
||||
*requestor,
|
||||
action_req.action_id,
|
||||
ActionReplyVariant::CompletionFailed { error_code, params },
|
||||
));
|
||||
if result.is_err() {
|
||||
log::error!("sending action reply failed");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_shell_command_execution(
|
||||
&self,
|
||||
requestor: &MessageMetadata,
|
||||
action_req: &ActionRequest,
|
||||
) {
|
||||
if let ActionRequestVariant::VecData(data) = &action_req.variant {
|
||||
let shell_cmd_result: serde_json::Result<ShellCmd> = serde_json::from_slice(data);
|
||||
match shell_cmd_result {
|
||||
Ok(shell_cmd) => {
|
||||
log::info!("executing shell cmd {:?}", shell_cmd);
|
||||
match Command::new(shell_cmd.cmd).args(shell_cmd.args).status() {
|
||||
Ok(status) => {
|
||||
if status.success() {
|
||||
self.send_completion_success(requestor, action_req);
|
||||
} else {
|
||||
log::warn!("execution of command failed: {}", status);
|
||||
self.send_completion_failure(
|
||||
requestor,
|
||||
action_req,
|
||||
SHELL_CMD_EXECUTION_FAILURE,
|
||||
Some(status.to_string().into()),
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("execution of command failed with IO error: {}", e);
|
||||
self.send_completion_failure(
|
||||
requestor,
|
||||
action_req,
|
||||
SHELL_CMD_IO_ERROR,
|
||||
Some(e.to_string().into()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("failed to deserialize shell command: {}", e);
|
||||
let result = self.action_reply_tx.send(GenericMessage::new_action_reply(
|
||||
*requestor,
|
||||
action_req.action_id,
|
||||
ActionReplyVariant::Completed,
|
||||
));
|
||||
if result.is_err() {
|
||||
log::error!("Sending action reply failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::warn!("no shell command was supplied for shell command action command");
|
||||
self.send_completion_failure(requestor, action_req, SHELL_CMD_INVALID_FORMAT, None);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_stop_file(&self) {
|
||||
let check_at_path = |path: &Path| {
|
||||
if path.exists() {
|
||||
@ -202,3 +293,61 @@ pub fn get_latest_image(index: usize) -> Result<PathBuf, std::io::Error> {
|
||||
}
|
||||
Err(std::io::Error::other("No latest image found"))
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::{mpsc, Arc};
|
||||
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn init() {
|
||||
env_logger::builder().is_test(true).init();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shell_cmd_exection() {
|
||||
init();
|
||||
let (composite_req_tx, composite_req_rx) = mpsc::channel();
|
||||
let (action_reply_tx, action_reply_rx) = mpsc::channel();
|
||||
let stop_signal = Arc::default();
|
||||
let mut exp_ctrl =
|
||||
ExperimentController::new(composite_req_rx, action_reply_tx, stop_signal);
|
||||
let named_temp_file = NamedTempFile::new().expect("creating temp file failed");
|
||||
let args = vec![named_temp_file
|
||||
.path()
|
||||
.to_str()
|
||||
.expect("converting path to str failed")];
|
||||
|
||||
let cmd = ShellCmd { cmd: "rm", args };
|
||||
let cmd_serialized = serde_json::to_string(&cmd).expect("serialization failed");
|
||||
let action_req = satrs::action::ActionRequest {
|
||||
action_id: ActionId::ExecuteShellCommandBlocking as u32,
|
||||
variant: satrs::action::ActionRequestVariant::VecData(cmd_serialized.into_bytes()),
|
||||
};
|
||||
composite_req_tx
|
||||
.send(GenericMessage::new(
|
||||
MessageMetadata::new(1, 2),
|
||||
CompositeRequest::Action(action_req),
|
||||
))
|
||||
.expect("sending action request failed");
|
||||
exp_ctrl.perform_operation();
|
||||
assert!(!named_temp_file.path().exists());
|
||||
let action_reply = action_reply_rx
|
||||
.try_recv()
|
||||
.expect("receiving action reply failed");
|
||||
assert_eq!(
|
||||
action_reply.message.action_id,
|
||||
ActionId::ExecuteShellCommandBlocking as u32
|
||||
);
|
||||
match action_reply.message.variant {
|
||||
ActionReplyVariant::Completed => (),
|
||||
_ => {
|
||||
panic!(
|
||||
"unexecpted action reply variant {:?}",
|
||||
action_reply.message.variant
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,9 +12,9 @@ pub fn setup_logger() -> Result<(), fern::InitError> {
|
||||
path_buf.push(
|
||||
format!(
|
||||
"output_{}.log",
|
||||
humantime::format_rfc3339_seconds(std::time::SystemTime::now()).to_string()
|
||||
humantime::format_rfc3339_seconds(std::time::SystemTime::now())
|
||||
)
|
||||
.replace(":", "_"),
|
||||
.replace(':', "_"),
|
||||
);
|
||||
println!("Creating logfile {:?}", path_buf);
|
||||
LOGFILE_PATH
|
||||
|
@ -5,7 +5,7 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use log::{debug, info};
|
||||
use log::info;
|
||||
use ops_sat_rs::config::{
|
||||
cfg_file::create_app_config,
|
||||
components::{CONTROLLER_ID, TCP_SERVER, TCP_SPP_CLIENT, UDP_SERVER},
|
||||
@ -53,9 +53,9 @@ mod requests;
|
||||
mod tmtc;
|
||||
|
||||
fn main() {
|
||||
setup_logger().expect("setting up logging with fern failed");
|
||||
let version_str = VERSION.unwrap_or("?");
|
||||
println!("OPS-SAT Rust Experiment OBSW v{}", version_str);
|
||||
setup_logger().expect("setting up logging with fern failed");
|
||||
create_low_priority_ground_dir();
|
||||
|
||||
let app_cfg = create_app_config();
|
||||
|
Loading…
Reference in New Issue
Block a user