Monorepo consolidation: workspace, shared types, transport plans, docker/swam assets
This commit is contained in:
183
control/api/tests/e2e_control_plane_fleet_docker.rs
Normal file
183
control/api/tests/e2e_control_plane_fleet_docker.rs
Normal file
@@ -0,0 +1,183 @@
|
||||
use jsonwebtoken::{EncodingKey, Header, encode};
|
||||
use serde::Serialize;
|
||||
use std::{fs, net::TcpListener, time::Duration};
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Claims {
|
||||
sub: String,
|
||||
session_id: String,
|
||||
permissions: Vec<String>,
|
||||
exp: usize,
|
||||
}
|
||||
|
||||
fn free_port() -> u16 {
|
||||
TcpListener::bind("127.0.0.1:0")
|
||||
.unwrap()
|
||||
.local_addr()
|
||||
.unwrap()
|
||||
.port()
|
||||
}
|
||||
|
||||
fn token(secret: &[u8], perms: &[&str]) -> String {
|
||||
let exp = (std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs()
|
||||
+ 60) as usize;
|
||||
|
||||
encode(
|
||||
&Header::default(),
|
||||
&Claims {
|
||||
sub: "op_1".to_string(),
|
||||
session_id: "sess_1".to_string(),
|
||||
permissions: perms.iter().map(|p| (*p).to_string()).collect(),
|
||||
exp,
|
||||
},
|
||||
&EncodingKey::from_secret(secret),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
async fn wait_ready(url: &str) {
|
||||
let client = reqwest::Client::new();
|
||||
let start = tokio::time::Instant::now();
|
||||
loop {
|
||||
let ok = client
|
||||
.get(format!("{url}/ready"))
|
||||
.send()
|
||||
.await
|
||||
.map(|r| r.status().is_success())
|
||||
.unwrap_or(false);
|
||||
if ok {
|
||||
return;
|
||||
}
|
||||
if start.elapsed() > Duration::from_secs(10) {
|
||||
panic!("control-api did not become ready");
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn control_plane_can_see_the_fleet_via_docker_stubs() {
|
||||
let enabled = std::env::var("CONTROL_TEST_DOCKER").ok();
|
||||
assert_eq!(enabled.as_deref(), Some("1"));
|
||||
|
||||
let nginx_conf = r#"
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
location = /health { return 200 "ok\n"; }
|
||||
location = /ready { return 200 "ready\n"; }
|
||||
location = /metrics { return 200 "stub_build_info{service=\"stub\",version=\"dev\",git_sha=\"000\"} 1\n"; }
|
||||
}
|
||||
"#;
|
||||
|
||||
let mut conf_path = std::env::temp_dir();
|
||||
conf_path.push(format!(
|
||||
"cloudlysis-control-nginx-{}.conf",
|
||||
uuid::Uuid::new_v4()
|
||||
));
|
||||
fs::write(&conf_path, nginx_conf).unwrap();
|
||||
|
||||
let gateway_port = free_port();
|
||||
let runner_port = free_port();
|
||||
let aggregate_port = free_port();
|
||||
let projection_port = free_port();
|
||||
|
||||
async fn run_stub(name: &str, port: u16, conf: &std::path::Path) -> String {
|
||||
let out = tokio::process::Command::new("docker")
|
||||
.args(["run", "-d", "--rm"])
|
||||
.args(["-p", &format!("{port}:80")])
|
||||
.args([
|
||||
"-v",
|
||||
&format!("{}:/etc/nginx/conf.d/default.conf:ro", conf.display()),
|
||||
])
|
||||
.arg("nginx:1.29-alpine")
|
||||
.output()
|
||||
.await
|
||||
.expect("failed to run docker");
|
||||
assert!(
|
||||
out.status.success(),
|
||||
"{name} stub failed: {}",
|
||||
String::from_utf8_lossy(&out.stderr)
|
||||
);
|
||||
String::from_utf8_lossy(&out.stdout).trim().to_string()
|
||||
}
|
||||
|
||||
let gateway_id = run_stub("gateway", gateway_port, &conf_path).await;
|
||||
let runner_id = run_stub("runner", runner_port, &conf_path).await;
|
||||
let aggregate_id = run_stub("aggregate", aggregate_port, &conf_path).await;
|
||||
let projection_id = run_stub("projection", projection_port, &conf_path).await;
|
||||
|
||||
let secret = b"e2e_secret";
|
||||
let api_port = free_port();
|
||||
let api_url = format!("http://127.0.0.1:{api_port}");
|
||||
|
||||
let mut placement_path = std::env::temp_dir();
|
||||
placement_path.push(format!(
|
||||
"cloudlysis-control-placement-{}.json",
|
||||
uuid::Uuid::new_v4()
|
||||
));
|
||||
fs::write(
|
||||
&placement_path,
|
||||
r#"{"revision":"e2e","aggregate_placement":{"placements":[]},"projection_placement":{"placements":[]},"runner_placement":{"placements":[]}}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut child = tokio::process::Command::new(env!("CARGO_BIN_EXE_api"))
|
||||
.env("CONTROL_API_ADDR", format!("127.0.0.1:{api_port}"))
|
||||
.env("CONTROL_GATEWAY_JWT_HS256_SECRET", "e2e_secret")
|
||||
.env("CONTROL_PLACEMENT_PATH", placement_path.to_string_lossy().to_string())
|
||||
.env(
|
||||
"CONTROL_FLEET_SERVICES",
|
||||
format!(
|
||||
"gateway=http://127.0.0.1:{gateway_port},aggregate=http://127.0.0.1:{aggregate_port},projection=http://127.0.0.1:{projection_port},runner=http://127.0.0.1:{runner_port}"
|
||||
),
|
||||
)
|
||||
.spawn()
|
||||
.expect("failed to spawn control-api");
|
||||
|
||||
wait_ready(&api_url).await;
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let t = token(secret, &["control:read"]);
|
||||
|
||||
let res = client
|
||||
.get(format!("{api_url}/admin/v1/fleet/snapshot"))
|
||||
.header(reqwest::header::AUTHORIZATION, format!("Bearer {t}"))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(res.status().is_success());
|
||||
|
||||
let v: serde_json::Value = res.json().await.unwrap();
|
||||
let services = v.get("services").and_then(|x| x.as_array()).unwrap();
|
||||
assert!(
|
||||
services.len() >= 5,
|
||||
"expected at least 5 services (including control-api), got {}",
|
||||
services.len()
|
||||
);
|
||||
|
||||
let res = client
|
||||
.get(format!("{api_url}/admin/v1/tenants"))
|
||||
.header(reqwest::header::AUTHORIZATION, format!("Bearer {t}"))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(res.status().is_success());
|
||||
|
||||
let _ = child.kill().await;
|
||||
|
||||
for id in [gateway_id, runner_id, aggregate_id, projection_id] {
|
||||
let _ = tokio::process::Command::new("docker")
|
||||
.args(["stop", &id])
|
||||
.output()
|
||||
.await;
|
||||
}
|
||||
|
||||
let _ = fs::remove_file(&conf_path);
|
||||
let _ = fs::remove_file(&placement_path);
|
||||
}
|
||||
Reference in New Issue
Block a user