mirror of
https://github.com/andrewyng/openworker.git
synced 2026-11-14 05:57:25 +00:00
352 lines
12 KiB
Rust
352 lines
12 KiB
Rust
use openworker_runtime::{
|
|
environment::{AttachmentState, Binding, Management, Phase, Sharing, Spec},
|
|
store::{Session, StateStore},
|
|
};
|
|
use openworker_store_postgres::{Config, PostgresStore, Tls};
|
|
use serde_json::json;
|
|
use std::sync::{Arc, Barrier};
|
|
|
|
fn config() -> Config {
|
|
let mut c = Config::new(
|
|
std::env::var("OW_PG_TEST_URL").expect("Disposable DB required"),
|
|
format!("ow_env_{}", uuid::Uuid::new_v4().simple()),
|
|
);
|
|
c.tls = Tls::LoopbackPlaintext;
|
|
PostgresStore::migrate(&c).unwrap();
|
|
c
|
|
}
|
|
fn spec(sharing: Sharing, management: Management) -> Spec {
|
|
Spec {
|
|
provider: "test-provider".into(),
|
|
provider_identity: "deployment-one".into(),
|
|
profile_digest: "profile-one".into(),
|
|
image: format!("example.invalid/runner@sha256:{}", "a".repeat(64)),
|
|
workspace_root: "/sandbox".into(),
|
|
sharing,
|
|
management,
|
|
}
|
|
}
|
|
fn ready(store: &PostgresStore, sharing: Sharing, management: Management) -> Binding {
|
|
let (e, fresh) = store
|
|
.begin_environment("create", "operator", spec(sharing, management))
|
|
.unwrap();
|
|
assert!(fresh);
|
|
let b = Binding {
|
|
environment_id: e.id.clone(),
|
|
spec: e.spec.clone(),
|
|
remote_id: "remote-unique-id".into(),
|
|
remote_name: "ow-test".into(),
|
|
workspace_scope: "default".into(),
|
|
policy_hash: "accepted-policy".into(),
|
|
policy_version: 1,
|
|
};
|
|
store
|
|
.complete_environment(&e.id, e.revision, Some(b.clone()))
|
|
.unwrap();
|
|
b
|
|
}
|
|
fn session(id: &str, b: Binding) -> Session {
|
|
let mut s:Session=serde_json::from_value(json!({"id":id,"name":"Remote session","workspace":"/sandbox","coworker":"test","bundle_digest":"bundle","schema_digest":null,"interactions":[],"turn":null,"sandbox":null,"closed":false,"resource_policy":openworker_runtime::resource_policy::Resolved::default()})).unwrap();
|
|
s.resource_policy.digest = b.fingerprint().unwrap();
|
|
s.resource_policy.workspace = "/sandbox".into();
|
|
s.environment = Some(b);
|
|
s
|
|
}
|
|
fn close(store: &mut PostgresStore, id: &str) {
|
|
let mut s = store.get(id).unwrap();
|
|
s.closed = true;
|
|
store.save(&s, "session_closed", json!({})).unwrap();
|
|
}
|
|
fn admin(c: &Config) -> postgres::Client {
|
|
let mut db = postgres::Client::connect(&c.connection, postgres::NoTls).unwrap();
|
|
db.batch_execute(&format!("SET search_path={},pg_catalog", c.schema))
|
|
.unwrap();
|
|
db
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "requires isolated OW_PG_TEST_URL"]
|
|
fn concurrent_creation_is_once_unknown_is_not_permission_to_recreate() {
|
|
let c = config();
|
|
let a = PostgresStore::open(c.clone()).unwrap();
|
|
let b = PostgresStore::open(c.clone()).unwrap();
|
|
let gate = Arc::new(Barrier::new(3));
|
|
let ga = gate.clone();
|
|
let gb = gate.clone();
|
|
let ta = std::thread::spawn(move || {
|
|
ga.wait();
|
|
a.begin_environment(
|
|
"create",
|
|
"operator",
|
|
spec(Sharing::Shared, Management::Harness),
|
|
)
|
|
.unwrap()
|
|
});
|
|
let tb = std::thread::spawn(move || {
|
|
gb.wait();
|
|
b.begin_environment(
|
|
"create",
|
|
"operator",
|
|
spec(Sharing::Shared, Management::Harness),
|
|
)
|
|
.unwrap()
|
|
});
|
|
gate.wait();
|
|
let (a, af) = ta.join().unwrap();
|
|
let (b, bf) = tb.join().unwrap();
|
|
assert_eq!(a, b);
|
|
assert_ne!(af, bf);
|
|
let p = PostgresStore::open(c).unwrap();
|
|
let unknown = p.complete_environment(&a.id, a.revision, None).unwrap();
|
|
assert_eq!(unknown.phase, Phase::Unknown);
|
|
let (existing, fresh) = p
|
|
.begin_environment("create", "operator", a.spec.clone())
|
|
.unwrap();
|
|
assert!(!fresh);
|
|
assert_eq!(existing, unknown);
|
|
assert!(p
|
|
.begin_environment("create", "different-actor", a.spec.clone())
|
|
.is_err());
|
|
assert!(p
|
|
.begin_environment_delete(&a.id, "delete", "operator")
|
|
.is_err());
|
|
let binding = Binding {
|
|
environment_id: a.id.clone(),
|
|
spec: a.spec,
|
|
remote_id: "observed-original-resource".into(),
|
|
remote_name: "ow-original".into(),
|
|
workspace_scope: "default".into(),
|
|
policy_hash: "policy".into(),
|
|
policy_version: 1,
|
|
};
|
|
assert!(p
|
|
.complete_environment(&a.id, a.revision, Some(binding.clone()))
|
|
.is_err());
|
|
let known = p
|
|
.complete_environment(&a.id, unknown.revision, Some(binding.clone()))
|
|
.unwrap();
|
|
assert_eq!(known.phase, Phase::Ready);
|
|
assert_eq!(
|
|
p.complete_environment(&a.id, unknown.revision, Some(binding))
|
|
.unwrap(),
|
|
known
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "requires isolated OW_PG_TEST_URL"]
|
|
fn shared_sessions_and_pending_connections_block_delete_until_explicit_release() {
|
|
let c = config();
|
|
let mut a = PostgresStore::open(c.clone()).unwrap();
|
|
let mut b = PostgresStore::open(c).unwrap();
|
|
let binding = ready(&a, Sharing::Shared, Management::Harness);
|
|
a.save(&session("a", binding.clone()), "session_created", json!({}))
|
|
.unwrap();
|
|
b.save(&session("b", binding.clone()), "session_created", json!({}))
|
|
.unwrap();
|
|
let (x, fresh) = a.reserve_attachment("a", "files").unwrap();
|
|
assert!(fresh);
|
|
assert_eq!(x.state, AttachmentState::Pending);
|
|
assert!(!a.reserve_attachment("a", "files").unwrap().1);
|
|
let (y, _) = b.reserve_attachment("b", "files").unwrap();
|
|
b.settle_attachment("b", &y.id, false, false).unwrap();
|
|
assert!(a
|
|
.begin_environment_delete(&binding.environment_id, "delete", "operator")
|
|
.is_err());
|
|
close(&mut a, "a");
|
|
// Closing A cannot release B or delete the shared environment.
|
|
a.settle_attachment("a", &x.id, true, true).unwrap();
|
|
assert_eq!(
|
|
b.reserve_attachment("b", "files").unwrap().0.state,
|
|
AttachmentState::Active
|
|
);
|
|
assert!(a
|
|
.begin_environment_delete(&binding.environment_id, "delete", "operator")
|
|
.is_err());
|
|
close(&mut b, "b");
|
|
assert!(a
|
|
.begin_environment_delete(&binding.environment_id, "delete", "operator")
|
|
.is_err());
|
|
b.settle_attachment("b", &y.id, true, false).unwrap();
|
|
let (deleting, fresh) = a
|
|
.begin_environment_delete(&binding.environment_id, "delete", "operator")
|
|
.unwrap();
|
|
assert!(fresh);
|
|
assert_eq!(deleting.phase, Phase::Deleting);
|
|
assert!(
|
|
!b.begin_environment_delete(&binding.environment_id, "delete", "operator")
|
|
.unwrap()
|
|
.1
|
|
);
|
|
assert!(b
|
|
.begin_environment_delete(&binding.environment_id, "different", "operator")
|
|
.is_err());
|
|
assert!(a
|
|
.save(
|
|
&session("late", binding.clone()),
|
|
"session_created",
|
|
json!({})
|
|
)
|
|
.is_err());
|
|
let deleted = a
|
|
.complete_environment_delete(&binding.environment_id, "delete", deleting.revision)
|
|
.unwrap();
|
|
assert_eq!(deleted.phase, Phase::Deleted);
|
|
assert_eq!(
|
|
a.complete_environment_delete(&binding.environment_id, "delete", deleting.revision)
|
|
.unwrap(),
|
|
deleted
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "requires isolated OW_PG_TEST_URL"]
|
|
fn exclusive_and_app_owned_environments_are_not_silently_shared_or_destroyed() {
|
|
let c = config();
|
|
let mut a = PostgresStore::open(c.clone()).unwrap();
|
|
let mut b = PostgresStore::open(c).unwrap();
|
|
let binding = ready(&a, Sharing::Exclusive, Management::Application);
|
|
a.save(&session("a", binding.clone()), "session_created", json!({}))
|
|
.unwrap();
|
|
assert!(b
|
|
.save(&session("b", binding.clone()), "session_created", json!({}))
|
|
.is_err());
|
|
let mut changed = a.get("a").unwrap();
|
|
changed.environment.as_mut().unwrap().remote_id = "replacement".into();
|
|
assert!(a.save(&changed, "rebind", json!({})).is_err());
|
|
close(&mut a, "a");
|
|
b.save(&session("b", binding.clone()), "session_created", json!({}))
|
|
.unwrap();
|
|
close(&mut b, "b");
|
|
assert!(a
|
|
.begin_environment_delete(&binding.environment_id, "delete", "operator")
|
|
.is_err());
|
|
assert_eq!(
|
|
a.environment(&binding.environment_id).unwrap().phase,
|
|
Phase::Ready
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "requires isolated OW_PG_TEST_URL"]
|
|
fn takeover_keeps_binding_and_fences_old_attachment_writes() {
|
|
let c = config();
|
|
let mut old = PostgresStore::open(c.clone()).unwrap();
|
|
let binding = ready(&old, Sharing::Shared, Management::Harness);
|
|
old.save(&session("s", binding.clone()), "session_created", json!({}))
|
|
.unwrap();
|
|
let (a, _) = old.reserve_attachment("s", "files").unwrap();
|
|
admin(&c)
|
|
.execute(
|
|
"UPDATE sessions SET expires=clock_timestamp()-interval '1 second' WHERE id='s'",
|
|
&[],
|
|
)
|
|
.unwrap();
|
|
let mut new = PostgresStore::open(c).unwrap();
|
|
new.claim("s").unwrap();
|
|
new.recover().unwrap();
|
|
assert_eq!(new.get("s").unwrap().environment, Some(binding.clone()));
|
|
assert!(old.settle_attachment("s", &a.id, false, false).is_err());
|
|
new.abandon_attachments("s").unwrap();
|
|
let (next, fresh) = new.reserve_attachment("s", "files").unwrap();
|
|
assert!(fresh);
|
|
assert_ne!(a.id, next.id);
|
|
assert!(next.owner_epoch > a.owner_epoch);
|
|
// Release/abandon are bookkeeping, not a claim that old remote effects stopped.
|
|
assert!(new
|
|
.events("s", 0)
|
|
.unwrap()
|
|
.iter()
|
|
.any(|e| e.kind == "attachments_abandoned" && e.data["outcome_unknown"] == true));
|
|
assert_eq!(
|
|
new.environment(&binding.environment_id).unwrap().phase,
|
|
Phase::Ready
|
|
);
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
#[ignore = "requires isolated OW_PG_TEST_URL and native Linux"]
|
|
async fn closing_recovered_session_releases_old_attachments_without_starting_tools() {
|
|
use openworker_runtime::{execution::Backend, model::Model, runtime::Runtime};
|
|
let c = config();
|
|
let mut old = PostgresStore::open(c.clone()).unwrap();
|
|
let binding = ready(&old, Sharing::Shared, Management::Harness);
|
|
old.save(&session("s", binding.clone()), "session_created", json!({}))
|
|
.unwrap();
|
|
let (attachment, _) = old.reserve_attachment("s", "files").unwrap();
|
|
old.settle_attachment("s", &attachment.id, false, false)
|
|
.unwrap();
|
|
tokio::task::block_in_place(|| {
|
|
admin(&c)
|
|
.execute(
|
|
"UPDATE sessions SET expires=clock_timestamp()-interval '1 second' WHERE id='s'",
|
|
&[],
|
|
)
|
|
.unwrap();
|
|
});
|
|
let new = PostgresStore::open(c.clone()).unwrap();
|
|
new.claim("s").unwrap();
|
|
let local = tempfile::tempdir().unwrap();
|
|
let bundle = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/assistant");
|
|
let runtime = Runtime::open_with_store(
|
|
local.path(),
|
|
&bundle,
|
|
&std::env::current_exe().unwrap(),
|
|
Model::new("fixture".into(), true),
|
|
Backend::Openshell,
|
|
None,
|
|
None,
|
|
Default::default(),
|
|
Some(Box::new(new.clone())),
|
|
)
|
|
.unwrap();
|
|
// No provider registry or exec endpoint is configured: close must not launch
|
|
// a replacement tool process merely to clear old ownership bookkeeping.
|
|
runtime
|
|
.dispatch("session/close", json!({"session_id":"s"}))
|
|
.await
|
|
.unwrap();
|
|
assert!(new.get("s").unwrap().closed);
|
|
let row = tokio::task::block_in_place(|| {
|
|
admin(&c)
|
|
.query_one(
|
|
"SELECT state,outcome_unknown FROM attachments WHERE id=$1",
|
|
&[&attachment.id],
|
|
)
|
|
.unwrap()
|
|
});
|
|
assert_eq!(row.get::<_, String>(0), "released");
|
|
assert!(row.get::<_, bool>(1));
|
|
assert!(new
|
|
.begin_environment_delete(&binding.environment_id, "delete", "operator")
|
|
.is_ok());
|
|
runtime.shutdown().await.unwrap();
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "requires isolated OW_PG_TEST_URL"]
|
|
fn delete_racing_session_creation_never_deletes_a_successfully_bound_live_session() {
|
|
let c = config();
|
|
let a = PostgresStore::open(c.clone()).unwrap();
|
|
let binding = ready(&a, Sharing::Shared, Management::Harness);
|
|
let mut creator = PostgresStore::open(c).unwrap();
|
|
let gate = Arc::new(Barrier::new(3));
|
|
let ga = gate.clone();
|
|
let gb = gate.clone();
|
|
let id = binding.environment_id.clone();
|
|
let create = std::thread::spawn(move || {
|
|
ga.wait();
|
|
creator
|
|
.save(&session("s", binding), "session_created", json!({}))
|
|
.is_ok()
|
|
});
|
|
let delete = std::thread::spawn(move || {
|
|
gb.wait();
|
|
a.begin_environment_delete(&id, "delete", "operator")
|
|
.is_ok()
|
|
});
|
|
gate.wait();
|
|
assert_ne!(create.join().unwrap(), delete.join().unwrap());
|
|
}
|