lazaret/crates/lazaret-host-coordinator/src/runtime/tests.rs

179 lines
5.7 KiB
Rust
Raw Normal View History

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());
}