Files
cloudlysis/control/api/tests/drift_docker_gated.rs
Vlad Durnea 2595e7f1c5
Some checks failed
ci / ui (push) Failing after 28s
ci / rust (push) Failing after 2m40s
images / build-and-push (push) Failing after 19s
feat(billing): implement tenant subscription entitlements system (milestones 0-6)
2026-03-30 18:41:23 +03:00

138 lines
4.7 KiB
Rust

#[tokio::test]
async fn platform_drift_docker_test_is_gated() {
use tower::ServiceExt;
let enabled = std::env::var("CONTROL_TEST_DOCKER").ok();
if enabled.as_deref() != Some("1") {
eprintln!("skipping: set CONTROL_TEST_DOCKER=1 to enable docker drift tests");
return;
}
// We only run the "real" drift check when Swarm is available locally.
// If Swarm isn't active, we skip to keep CI/dev machines happy.
let info = std::process::Command::new("docker")
.args(["info", "--format", "{{.Swarm.LocalNodeState}}"])
.output();
let Ok(info) = info else {
eprintln!("skipping: docker not available");
return;
};
if !info.status.success() {
eprintln!("skipping: docker info failed");
return;
}
let state = String::from_utf8_lossy(&info.stdout).trim().to_string();
if state != "active" {
eprintln!("skipping: docker swarm not active (LocalNodeState={state})");
return;
}
// Create a short-lived service so drift can see an "extra" observed service.
let name = format!("cloudlysis-drift-extra-{}", uuid::Uuid::new_v4());
let create = std::process::Command::new("docker")
.args([
"service",
"create",
"--name",
&name,
"--restart-condition",
"none",
"busybox:1.36",
"sh",
"-c",
"sleep 60",
])
.output()
.expect("docker service create");
if !create.status.success() {
eprintln!("skipping: failed to create swarm service (maybe permissions?)");
return;
}
// Ensure cleanup even if assertion fails.
struct Cleanup(String);
impl Drop for Cleanup {
fn drop(&mut self) {
let _ = std::process::Command::new("docker")
.args(["service", "rm", &self.0])
.output();
}
}
let _cleanup = Cleanup(name.clone());
// Now call drift via a minimal in-process app configured for docker-cli swarm observation.
let handle = metrics_exporter_prometheus::PrometheusBuilder::new()
.install_recorder()
.expect("failed to install prometheus recorder");
let app = api::build_app(api::AppState {
prometheus: handle,
auth: api::AuthConfig {
hs256_secret: Some(b"test_secret".to_vec()),
},
jobs: api::JobStore::default(),
audit: api::AuditStore::default(),
tenant_locks: api::TenantLocks::default(),
config_locks: api::ConfigLocks::default(),
http: reqwest::Client::new(),
placement: api::PlacementStore::new(
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.and_then(|p| p.parent())
.unwrap()
.join("config/placement/dev.json"),
),
billing: api::billing::BillingStore::new(
std::env::temp_dir().join("billing-drift-test.json"),
),
billing_provider: std::sync::Arc::new(api::billing::MockProvider),
billing_enforcement_enabled: false,
config: api::ConfigRegistry::new(None, None),
fleet_services: vec![],
swarm: api::SwarmStore::new_docker_cli(),
docs: None,
});
// Auth token (control:read).
let exp = (std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs()
+ 60) as usize;
let token = jsonwebtoken::encode(
&jsonwebtoken::Header::default(),
&serde_json::json!({
"sub": "user_1",
"session_id": "sess_1",
"permissions": ["control:read"],
"exp": exp
}),
&jsonwebtoken::EncodingKey::from_secret(b"test_secret"),
)
.unwrap();
let res = app
.oneshot(
axum::http::Request::builder()
.uri("/admin/v1/platform/drift")
.header(axum::http::header::AUTHORIZATION, format!("Bearer {token}"))
.body(axum::body::Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(res.status(), axum::http::StatusCode::OK);
let body = axum::body::to_bytes(res.into_body(), 1024 * 1024)
.await
.unwrap();
let v: serde_json::Value = serde_json::from_slice(&body).unwrap();
let items = v.get("items").and_then(|x| x.as_array()).unwrap();
assert!(
items.iter().any(|i| {
i.get("kind").and_then(|k| k.as_str()) == Some("extra")
&& i.get("service").and_then(|s| s.as_str()) == Some(name.as_str())
}),
"expected drift to include extra service {name}, got: {v}"
);
}