use std::fs::{File, OpenOptions}; use std::path::Path; use lazaret_core::identity::{ BootId, ConfigurationFingerprint, CoordinatorId, DomainId, HostPidLocator, NetworkSidecarId, PidfsDeviceId, PidfsInodeId, PidfsProcessAuthorityV1, ProcessStartTimeTicks, SandboxId, }; use lazaret_core::records::{Domain, DurableDecision, Generation}; use lazaret_linux::filesystem::try_exclusive_flock; use tempfile::TempDir; use super::*; fn bytes(value: u8) -> [u8; 32] { [value; 32] } fn process(value: u32) -> PidfsProcessIdentityV1 { PidfsProcessIdentityV1::new( PidfsProcessAuthorityV1 { boot_id: BootId::new([1; 16]), pidfs_st_dev: PidfsDeviceId::new(u64::from(value)), pidfs_st_ino: PidfsInodeId::new(u64::from(value) + 100), }, HostPidLocator::new(value), ProcessStartTimeTicks::new(u64::from(value) + 200), ) } fn domain() -> Domain { Domain { domain_id: DomainId::new(bytes(1)), profile_name: "trusted".into(), canonical_workspace: HostPath::new(b"/workspace/project".to_vec()), } } fn generation() -> Generation { Generation { domain_id: domain().domain_id, generation_id: GenerationId::new(7), configuration_fingerprint: ConfigurationFingerprint::new(bytes(2)), coordinator_id: CoordinatorId::new(bytes(3)), network_sidecar_id: NetworkSidecarId::new(bytes(4)), sandbox_id: SandboxId::new(bytes(5)), durable_decision: DurableDecision::None, } } fn lock_file(path: &Path) -> File { OpenOptions::new() .read(true) .write(true) .create(true) .truncate(false) .open(path) .unwrap() } fn store(directory: &TempDir) -> HostCoordinatorDomainStore { let lock = try_exclusive_flock(lock_file(&directory.path().join("domain.lock"))).unwrap(); let store = HostCoordinatorDomainStore::open(lock, &directory.path().join("domain.redb"), &domain()) .unwrap(); store .apply(&[DomainStoreMutation::PutGeneration(generation())]) .unwrap(); store } fn staged( slot: HostProcessOwnershipSlot, identity: PidfsProcessIdentityV1, ) -> StagedProcessOwnership { StagedProcessOwnership::v1( domain().domain_id, generation().generation_id, generation().coordinator_id, slot, identity, ) } #[test] fn sandbox_staging_readiness_and_release_preserve_exact_ownership() { let directory = tempfile::tempdir().unwrap(); let store = store(&directory); let coordinator = SandboxCoordinator::new(&store); coordinator .begin_intent( generation().generation_id, ContainerId::new(bytes(6)), directory.path(), BundleDigest::new(bytes(7)), process(100), ) .unwrap(); let rs = staged( HostProcessOwnershipSlot::SandboxRuntimeSupervisor, process(101), ); coordinator.stage_runtime_supervisor(rs).unwrap(); assert!( coordinator .acknowledge_runtime_supervisor(rs) .unwrap() .acknowledges(&rs) ); let runsc = staged(HostProcessOwnershipSlot::SandboxAttachedRunsc, process(102)); assert!( coordinator .stage_attached_runsc(runsc) .unwrap() .acknowledges(&runsc) ); let intent = coordinator.participant(generation().generation_id).unwrap(); let Some(ParticipantOwnership::Sandbox(ownership)) = intent.ownership() else { panic!("Sandbox ownership missing"); }; assert_eq!(intent.state(), ParticipantState::Intent); assert_eq!( ownership.runtime_supervisor, PersistedHostProcessOwnership::committed(process(101)) ); assert_eq!( ownership.attached_runsc, PersistedHostProcessOwnership::staged(process(102)) ); coordinator.commit_ready(runsc).unwrap(); assert_eq!( coordinator .participant(generation().generation_id) .unwrap() .state(), ParticipantState::Committed ); coordinator .begin_release(generation().generation_id) .unwrap(); coordinator .complete_clean_after_absence(generation().generation_id) .unwrap(); let clean = coordinator.participant(generation().generation_id).unwrap(); assert_eq!(clean.state(), ParticipantState::Clean); assert!(clean.ownership().unwrap().is_empty()); } #[test] fn mismatched_and_skipped_acquisition_boundaries_refuse_without_state_change() { let directory = tempfile::tempdir().unwrap(); let store = store(&directory); let coordinator = SandboxCoordinator::new(&store); coordinator .begin_intent( generation().generation_id, ContainerId::new(bytes(6)), directory.path(), BundleDigest::new(bytes(7)), process(100), ) .unwrap(); let runsc = staged(HostProcessOwnershipSlot::SandboxAttachedRunsc, process(102)); assert!(coordinator.stage_attached_runsc(runsc).is_err()); assert!(coordinator.commit_ready(runsc).is_err()); let wrong_domain = StagedProcessOwnership::v1( DomainId::new(bytes(9)), generation().generation_id, generation().coordinator_id, HostProcessOwnershipSlot::SandboxRuntimeSupervisor, process(101), ); assert!(coordinator.stage_runtime_supervisor(wrong_domain).is_err()); let participant = coordinator.participant(generation().generation_id).unwrap(); let Some(ParticipantOwnership::Sandbox(ownership)) = participant.ownership() else { panic!("Sandbox ownership missing"); }; assert!(ownership.runtime_supervisor.is_unassigned()); assert!(ownership.attached_runsc.is_unassigned()); }