tools/cubec: fix CLI so ./check stress works; separate client flags from command
cubec forwarded its OWN leading options (--socket/--tenant/...) verbatim into the command payload sent to the daemon, so 'cubec --socket SOCK "prog ..."' made the server reject '--socket' as an unknown command. stress.sh therefore produced 'error: unknown command: --socket' on every sample and measured nothing. Split cubec arg parsing into client-option vs command-payload so flags are consumed locally and only the command reaches the daemon. The canonical './check stress' stage now drives a real daemon and samples stats.
This commit is contained in:
+64
-19
@@ -13,29 +13,74 @@ use cubesys::net::{read_stream_frame, write_frame};
|
||||
use std::io::{BufRead, Write};
|
||||
use std::os::unix::net::UnixStream;
|
||||
|
||||
/// Resolve `--key VALUE` from argv, or None.
|
||||
fn arg_value(args: &[String], key: &str) -> Option<String> {
|
||||
args.iter()
|
||||
.position(|a| a == key)
|
||||
.and_then(|i| args.get(i + 1).cloned())
|
||||
}
|
||||
|
||||
/// `cubec` — a tiny Unix-domain-socket client for the `cube-server` daemon.
|
||||
///
|
||||
/// Separates cubec's OWN options (`--socket`, `--tenant`, `--owner`,
|
||||
/// `--auth-key*`) from the command it forwards to the daemon. Conflating the
|
||||
/// two made `cubec --socket SOCK "prog ..."` send `--socket SOCK prog ...` to
|
||||
/// the server, which rejected it as an unknown command.
|
||||
fn main() {
|
||||
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||
let socket = arg_value(&args, "--socket")
|
||||
.or_else(|| std::env::var("CUBE_SOCKET").ok())
|
||||
.unwrap_or_else(|| "/run/cube/demo.sock".to_string());
|
||||
let tenant = arg_value(&args, "--tenant").unwrap_or_else(|| "default".to_string());
|
||||
let owner = arg_value(&args, "--owner")
|
||||
.or_else(|| std::env::var("USER").ok())
|
||||
.unwrap_or_else(|| "cubec".to_string());
|
||||
let raw: Vec<String> = std::env::args().skip(1).collect();
|
||||
|
||||
// Defaults from the environment (kept so legacy invocations still work).
|
||||
let mut socket =
|
||||
std::env::var("CUBE_SOCKET").unwrap_or_else(|_| "/run/cube/demo.sock".to_string());
|
||||
let mut tenant = "default".to_string();
|
||||
let mut owner = std::env::var("USER").unwrap_or_else(|_| "cubec".to_string());
|
||||
let mut psk_path: Option<String> = None;
|
||||
let mut psk_env: Option<String> = None;
|
||||
|
||||
// Everything that is not one of cubec's options becomes the command text
|
||||
// forwarded to the daemon.
|
||||
let mut cmd_args: Vec<String> = Vec::new();
|
||||
let mut i = 0;
|
||||
while i < raw.len() {
|
||||
match raw[i].as_str() {
|
||||
"--socket" => {
|
||||
if let Some(v) = raw.get(i + 1) {
|
||||
socket = v.clone();
|
||||
}
|
||||
i += 2;
|
||||
}
|
||||
"--tenant" => {
|
||||
if let Some(v) = raw.get(i + 1) {
|
||||
tenant = v.clone();
|
||||
}
|
||||
i += 2;
|
||||
}
|
||||
"--owner" => {
|
||||
if let Some(v) = raw.get(i + 1) {
|
||||
owner = v.clone();
|
||||
}
|
||||
i += 2;
|
||||
}
|
||||
"--auth-key" => {
|
||||
if let Some(v) = raw.get(i + 1) {
|
||||
psk_path = Some(v.clone());
|
||||
}
|
||||
i += 2;
|
||||
}
|
||||
"--auth-key-env" => {
|
||||
if let Some(v) = raw.get(i + 1) {
|
||||
psk_env = Some(v.clone());
|
||||
}
|
||||
i += 2;
|
||||
}
|
||||
// Not a cubec option: part of the command payload.
|
||||
other => {
|
||||
cmd_args.push(other.to_string());
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-shared key for the signed-HELLO handshake (plan R4). `--auth-key PATH`
|
||||
// reads a file; `--auth-key-env VAR` reads an env var; otherwise `CUBE_AUTH_KEY`.
|
||||
let psk: Option<Vec<u8>> = if let Some(path) = arg_value(&args, "--auth-key") {
|
||||
let psk: Option<Vec<u8>> = if let Some(path) = psk_path {
|
||||
std::fs::read_to_string(&path)
|
||||
.map(|s| s.trim().as_bytes().to_vec())
|
||||
.ok()
|
||||
} else if let Some(var) = arg_value(&args, "--auth-key-env") {
|
||||
} else if let Some(var) = psk_env {
|
||||
std::env::var(&var)
|
||||
.ok()
|
||||
.map(|s| s.trim().as_bytes().to_vec())
|
||||
@@ -79,7 +124,7 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
if args.is_empty() {
|
||||
if cmd_args.is_empty() {
|
||||
// REPL mode (legacy behaviour, now after an optional handshake).
|
||||
let stdin = std::io::stdin();
|
||||
for line in stdin.lock().lines().map_while(Result::ok) {
|
||||
@@ -101,7 +146,7 @@ fn main() {
|
||||
}
|
||||
} else {
|
||||
// One-shot: join the remaining args as the command line, send, print.
|
||||
let cmd = args.join(" ");
|
||||
let cmd = cmd_args.join(" ");
|
||||
if write_frame(&mut stream, &cmd).is_err() {
|
||||
eprintln!("cubec: write failed");
|
||||
std::process::exit(1);
|
||||
|
||||
@@ -47,6 +47,9 @@ PAIRS=0
|
||||
SAMPLE_EVERY=25 # sample stats every 25 pairs (~ once a second at load)
|
||||
while [ "$(date +%s)" -lt "$END" ]; do
|
||||
X=$((PAIRS % 256))
|
||||
# Flags MUST precede the command: `cubec` parses --socket/--tenant as leading
|
||||
# options, so `"$CLI" --socket SOCK "prog ..."` would turn the trailing
|
||||
# --socket into an unknown subcommand. Pass flags first.
|
||||
"$CLI" --socket "$SOCK" "prog /c077/z001/y001/x$X const $((PAIRS % 97)) halt" >/dev/null 2>&1
|
||||
"$CLI" --socket "$SOCK" "run /c077/z001/y001/x$X" >/dev/null 2>&1
|
||||
PAIRS=$((PAIRS + 1))
|
||||
|
||||
Reference in New Issue
Block a user