Keep SAU-358 supervision and SAU-357's five-second host policy together; adapt supervisor tests to the metadata-independent snapshot API. Card: SAU-357
155 lines
6.1 KiB
Rust
155 lines
6.1 KiB
Rust
//! Raw discovery and lifecycle guest for activation protocol tests.
|
|
use serde_json::{Value, json};
|
|
use std::io::{self, BufRead, Write};
|
|
/// Emits one flushed JSON-RPC response.
|
|
fn send(value: Value) {
|
|
println!("{value}");
|
|
io::stdout().flush().unwrap();
|
|
}
|
|
/// Calls a provider after activation releases an old consumer process.
|
|
fn deferred(request: &Value, plugin: &str, lines: &mut impl Iterator<Item = io::Result<String>>) {
|
|
send(
|
|
json!({"jsonrpc":"2.0","id":request["id"],"result":std::env::var("SAUSAGE_PID").unwrap()}),
|
|
);
|
|
let before = std::time::Instant::now();
|
|
while !std::path::Path::new(&format!("/state/{plugin}/release-call")).exists() {
|
|
assert!(before.elapsed() < std::time::Duration::from_secs(30));
|
|
}
|
|
send(json!({"jsonrpc":"2.0","id":"deferred","method":"notes.legacy","params":{}}));
|
|
for line in lines {
|
|
let response: Value = serde_json::from_str(&line.unwrap()).unwrap();
|
|
if response["id"] == "deferred" {
|
|
std::fs::write(format!("/state/{plugin}/call-result"), response.to_string()).unwrap();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
/// Answers a method that version two removes from its interface.
|
|
fn legacy(request: &Value, document: &Value, version: &str) {
|
|
if document["methods"]
|
|
.as_array()
|
|
.unwrap()
|
|
.iter()
|
|
.any(|m| m["name"] == "legacy")
|
|
{
|
|
send(json!({"jsonrpc":"2.0","id":request["id"],"result":version}));
|
|
} else {
|
|
send(
|
|
json!({"jsonrpc":"2.0","id":request["id"],"error":{"code":-32601,"message":"legacy removed","data":{"version":version}}}),
|
|
);
|
|
}
|
|
}
|
|
/// Reads a version-specific discovery document without the SDK.
|
|
fn main() {
|
|
let version = env!("FIXTURE_VERSION");
|
|
let plugin = std::env::var("SAUSAGE_PLUGIN").unwrap();
|
|
let mut lines = io::stdin().lock().lines();
|
|
let request: Value = serde_json::from_str(&lines.next().unwrap().unwrap()).unwrap();
|
|
let document: Value =
|
|
serde_json::from_slice(&std::fs::read(format!("/state/{plugin}/{version}.json")).unwrap())
|
|
.unwrap();
|
|
let result = match request["method"].as_str().unwrap() {
|
|
"deferred" => {
|
|
deferred(&request, &plugin, &mut lines);
|
|
return;
|
|
}
|
|
"legacy" => {
|
|
legacy(&request, &document, version);
|
|
return;
|
|
}
|
|
"rpc.discover" => {
|
|
if document["x-test-no-answer"] == true {
|
|
for _ in &mut lines {}
|
|
return;
|
|
}
|
|
if document["x-test-wait"] == true {
|
|
std::fs::write(format!("/state/{plugin}/discover-started"), "ready").unwrap();
|
|
let before = std::time::Instant::now();
|
|
while !std::path::Path::new(&format!("/state/{plugin}/discover-release")).exists() {
|
|
assert!(before.elapsed() < std::time::Duration::from_secs(30));
|
|
}
|
|
}
|
|
document
|
|
}
|
|
"read" => {
|
|
let text =
|
|
std::fs::read_to_string(request["params"]["path"].as_str().unwrap()).unwrap();
|
|
serde_json::from_str(&text).unwrap_or(json!(text))
|
|
}
|
|
"rpc.retired" => {
|
|
std::fs::write(
|
|
format!("/state/{plugin}/retired"),
|
|
request["params"]["previous"].as_str().unwrap(),
|
|
)
|
|
.unwrap();
|
|
json!({"retired":version})
|
|
}
|
|
"work" => {
|
|
// Keep an actual request unanswered through grace; notices do not drain it.
|
|
send(
|
|
json!({"jsonrpc":"2.0","method":"core.notify","params":{"name":"ready","params":{}}}),
|
|
);
|
|
for _ in lines {}
|
|
return;
|
|
}
|
|
"serve" | "hold" => {
|
|
// One genuine process failure, followed by a healthy service, without
|
|
// timers or liveness polling in either the fixture or supervisor.
|
|
let crash = request["params"]["crash_once"].as_str();
|
|
let first = crash.is_some()
|
|
&& std::fs::OpenOptions::new()
|
|
.write(true)
|
|
.create_new(true)
|
|
.open(format!("/state/{plugin}/crashed-once"))
|
|
.is_ok();
|
|
if first && crash == Some("before") {
|
|
std::process::exit(1);
|
|
}
|
|
send(json!({"jsonrpc":"2.0","id":request["id"],"result":{"version":version}}));
|
|
if first && crash == Some("after") {
|
|
std::process::exit(1);
|
|
}
|
|
if request["method"] == "serve" {
|
|
std::fs::write(format!("/state/{plugin}/service"), version).unwrap();
|
|
}
|
|
for line in lines {
|
|
let Ok(message) = serde_json::from_str::<Value>(&line.unwrap()) else {
|
|
continue;
|
|
};
|
|
if message["method"] == "rpc.superseded" {
|
|
std::fs::write(
|
|
format!(
|
|
"/state/{plugin}/superseded-{}",
|
|
std::env::var("SAUSAGE_PID").unwrap()
|
|
),
|
|
message.to_string(),
|
|
)
|
|
.unwrap();
|
|
if request["params"]["exit_on_superseded"] == true {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
std::fs::write(format!("/state/{plugin}/ended-{version}"), "ended").unwrap();
|
|
return;
|
|
}
|
|
_ => json!({"version":version}),
|
|
};
|
|
send(json!({"jsonrpc":"2.0","id":request["id"],"result":result}));
|
|
if request["method"] == "rpc.discover" && result["x-test-linger"] == true {
|
|
if result["x-test-stubborn"] == true {
|
|
let before = std::time::Instant::now();
|
|
while !std::path::Path::new(&format!("/state/{plugin}/discovery-exit")).exists() {
|
|
assert!(before.elapsed() < std::time::Duration::from_secs(30));
|
|
}
|
|
} else {
|
|
for _ in lines {}
|
|
}
|
|
std::fs::write(
|
|
format!("/state/{plugin}/discovery-ended-{version}"),
|
|
"ended",
|
|
)
|
|
.unwrap();
|
|
}
|
|
}
|