Skip to content

Commit 370c141

Browse files
committed
Integrate a Rhai interpreter into faux-mgs.
Rust code changes: - add Rhai scripting as a feature (--features=rhaiscript) - add ArchiveInspector for access to RawHubrisArchive - add "system(argv) -> #{exit_code, stdout, stderr}" from std::process::Command - export faux-mgs paramsters to Rhai main. - run any faux-mgs command with "let result = faux_mgs(["arg0", .. "argN"]); - faux-mgs results are passed back to the script as a map even if they are simpler JSON. - ChronoPackage for time handling. - FilesystemPackage for file access. - EnvironmentPackage for env var access. - export "scriptdir" so that script can get other files relative to itself. - "verify_rot_image()" to verify a RoT image vs CFPA, CMPA. - vars available to main(): - "argv" - script main's scope passing all remaining CLI args. - "rbi_default" - expose faux-mgs default "rot_boot_info" version - "interface" - pass the "--interface INTERFACE" value. - "reset_watchdog_timeout_ms" - pass that value to the script. - Route Rhai's debug function to the faux-mgs log. - The `debug("message")` function is routed to the faux-mgs slog logging. Prefixing a message with "crit|", "trace|", "error|", "warn|", "error|", or "debug|" will log at that corresponding level. Leaving off the prefix or using some other prefix will log at the debug level. - Rhai's `print()` still goes to stdout. Rhai scripts: scripts/util.rhai contains common script and faux-mgs support. - getops() - to_hexstring() - cstring_to_string(a) - array_to_mac(a) - ab_to_01(v) - env_expand(s, override) - rot_boot_info() - state() - caboose_value(component, slot, key) - get_device_cabooses() - rkth_to_key_name(rkth) - array_to_blob(a) - get_cmpa() - get_cfpa() - get_rot_keyset() scripts/update-rollback.rhai - Only use MGS messages for testing, no humility or other APIs - perform happy path update and rollback from baseline to under-test images. scripts/targets.json - an example configuration script for scripts/update-rollback.rhai
1 parent 3a686ca commit 370c141

File tree

10 files changed

+2130
-16
lines changed

10 files changed

+2130
-16
lines changed

Cargo.lock

+261-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+9-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ hubtools = { git = "https://github.com/oxidecomputer/hubtools.git", branch = "ma
1919
slog-error-chain = { git = "https://github.com/oxidecomputer/slog-error-chain.git", branch = "main", features = ["derive"] }
2020

2121
anyhow = "1.0"
22+
async-recursion = "1.1.0"
2223
async-trait = "0.1"
2324
backoff = { version = "0.4.0", features = ["tokio"] }
2425
bitflags = "2.9.0"
@@ -30,13 +31,19 @@ glob = "0.3.2"
3031
hex = "0.4.3"
3132
hubpack = "0.1.2"
3233
humantime = "2.1.0"
34+
lpc55_areas = { git = "https://github.com/oxidecomputer/lpc55_support/", version = "0.2.3" }
35+
lpc55_sign = { git = "https://github.com/oxidecomputer/lpc55_support/", version = "0.3.0" }
3336
lru-cache = "0.1.2"
3437
lzss = "0.8"
3538
nix = { version = "0.27.1", features = ["net"] }
3639
omicron-zone-package = "0.11.0"
3740
once_cell = "1.21.1"
3841
paste = "1.0.15"
3942
rand = "0.8.5"
43+
rhai-chrono = { version = "^0" }
44+
rhai-env = "0.1.2"
45+
rhai-fs = { version = "0.1.3", features = ["metadata"] }
46+
rhai = { version = "1.21.0", features = ["serde", "metadata", "debugging"]}
4047
serde = { version = "1.0", default-features = false, features = ["derive"] }
4148
serde-big-array = "0.5.1"
4249
serde_json = "1.0.140"
@@ -53,10 +60,11 @@ static_assertions = "1.1.0"
5360
strum_macros = "0.25"
5461
string_cache = "0.8.8"
5562
termios = "0.3"
56-
thiserror = "1.0.69"
63+
thiserror = { version = "1.0.69", default-features = false }
5764
tokio = { version = "1.29", features = ["full"] }
5865
tokio-stream = { version = "0.1", features = ["fs"] }
5966
tokio-util = { version = "0.7", features = ["compat"] }
67+
toml = { version = "0.7", default-features = false, features = ["parse", "display"] }
6068
usdt = "0.5.0"
6169
uuid = { version = "1.16", default-features = false }
6270
version_check = "0.9.5"

faux-mgs/Cargo.toml

+16
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,19 @@ zerocopy.workspace = true
3131

3232
gateway-messages = { workspace = true, features = ["std"] }
3333
gateway-sp-comms.workspace = true
34+
35+
async-recursion = { workspace = true, optional = true }
36+
hubtools = { workspace = true, optional = true }
37+
lpc55_areas = { workspace = true, optional = true }
38+
lpc55_sign= { workspace = true, optional = true }
39+
rhai-chrono = { workspace = true, optional = true }
40+
rhai-env = { workspace = true, optional = true }
41+
rhai-fs = { workspace = true, optional = true }
42+
rhai = { workspace = true, optional = true }
43+
thiserror = { workspace = true, optional = true }
44+
toml = { workspace = true, optional = true }
45+
46+
[features]
47+
# XXX remove rhaiscript as a defailt feature
48+
default = ["rhaiscript"]
49+
rhaiscript = [ "dep:async-recursion", "dep:hubtools", "dep:lpc55_areas", "dep:lpc55_sign", "dep:rhai", "dep:rhai-chrono", "dep:rhai-env", "dep:rhai-fs", "dep:thiserror", "dep:toml"]

faux-mgs/src/main.rs

+48-13
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ use uuid::Uuid;
6363
use zerocopy::AsBytes;
6464

6565
mod picocom_map;
66+
#[cfg(feature = "rhaiscript")]
67+
mod rhaiscript;
6668
mod usart;
6769

6870
/// Command line program that can send MGS messages to a single SP.
@@ -128,6 +130,14 @@ struct Args {
128130
command: Command,
129131
}
130132

133+
/// Command line program that can send MGS messages to a single SP.
134+
#[cfg(feature = "rhaiscript")]
135+
#[derive(Parser, Debug)]
136+
struct RhaiArgs {
137+
#[clap(subcommand)]
138+
command: Command,
139+
}
140+
131141
fn level_from_str(s: &str) -> Result<Level> {
132142
if let Ok(level) = s.parse() {
133143
Ok(level)
@@ -383,6 +393,16 @@ enum Command {
383393
disable_watchdog: bool,
384394
},
385395

396+
/// Run a Rhai script within faux-mgs
397+
#[cfg(feature = "rhaiscript")]
398+
Rhai {
399+
/// Path to Rhia script
400+
script: PathBuf,
401+
/// Additional arguments passed to Rhia scripe
402+
#[clap(trailing_var_arg = true, allow_hyphen_values = true)]
403+
script_args: Vec<String>,
404+
},
405+
386406
/// Controls the system LED
387407
SystemLed {
388408
#[clap(subcommand)]
@@ -826,7 +846,7 @@ async fn main() -> Result<()> {
826846
.into_iter()
827847
.map(|sp| {
828848
let interface = sp.interface().to_string();
829-
run_command(
849+
run_any_command(
830850
sp,
831851
args.command.clone(),
832852
args.json.is_some(),
@@ -900,11 +920,28 @@ fn ssh_list_keys(socket: &PathBuf) -> Result<Vec<ssh_key::PublicKey>> {
900920
client.list_identities().context("failed to list identities")
901921
}
902922

903-
async fn run_command(
923+
/// This function exists to break recursive calls to the Rhai interpreter.
924+
/// main() calls here but Rhai{...} calls run_command().
925+
async fn run_any_command(
904926
sp: SingleSp,
905927
command: Command,
906928
json: bool,
907929
log: Logger,
930+
) -> Result<Output> {
931+
match command {
932+
#[cfg(feature = "rhaiscript")]
933+
Command::Rhai { script, script_args } => {
934+
rhaiscript::interpreter(&sp, log, script, script_args).await
935+
}
936+
_ => run_command(&sp, command, json, log).await,
937+
}
938+
}
939+
940+
async fn run_command(
941+
sp: &SingleSp,
942+
command: Command,
943+
json: bool,
944+
log: Logger,
908945
) -> Result<Output> {
909946
match command {
910947
// Skip special commands handled by `main()` above.
@@ -1261,7 +1298,7 @@ async fn run_command(
12611298
let data = fs::read(&image).with_context(|| {
12621299
format!("failed to read {}", image.display())
12631300
})?;
1264-
update(&log, &sp, component, slot, data).await.with_context(
1301+
update(&log, sp, component, slot, data).await.with_context(
12651302
|| {
12661303
format!(
12671304
"updating {} slot {} to {} failed",
@@ -1305,7 +1342,7 @@ async fn run_command(
13051342
..
13061343
} => {
13071344
let id = Uuid::from(id);
1308-
format!("update {id} aux flash scan complete (found_match={found_match}")
1345+
format!("update {id} aux flash scan complete (found_match={found_match})")
13091346
}
13101347
UpdateStatus::InProgress(sub_status) => {
13111348
let id = Uuid::from(sub_status.id);
@@ -1386,7 +1423,10 @@ async fn run_command(
13861423
Ok(Output::Lines(vec!["reset complete".to_string()]))
13871424
}
13881425
}
1389-
1426+
#[cfg(feature = "rhaiscript")]
1427+
Command::Rhai { script, script_args } => {
1428+
rhaiscript::interpreter(sp, log, script, script_args).await
1429+
}
13901430
Command::ResetComponent { component, disable_watchdog } => {
13911431
sp.reset_component_prepare(component).await?;
13921432
info!(log, "SP is prepared to reset component {component}",);
@@ -1475,14 +1515,8 @@ async fn run_command(
14751515
if time_sec == 0 {
14761516
bail!("--time must be >= 1 second");
14771517
}
1478-
monorail_unlock(
1479-
&log,
1480-
&sp,
1481-
time_sec,
1482-
ssh_auth_sock,
1483-
key,
1484-
)
1485-
.await?;
1518+
monorail_unlock(&log, sp, time_sec, ssh_auth_sock, key)
1519+
.await?;
14861520
}
14871521
}
14881522
}
@@ -1971,6 +2005,7 @@ async fn populate_phase2_images(
19712005
Ok(())
19722006
}
19732007

2008+
#[derive(Clone)]
19742009
enum Output {
19752010
Json(serde_json::Value),
19762011
Lines(Vec<String>),

0 commit comments

Comments
 (0)