Compose selected State Anchor paths over prepared roots

This commit is contained in:
ms2k 2026-07-17 03:58:27 +09:00
commit 4dd2b150d2
8 changed files with 1575 additions and 163 deletions

View file

@ -67,6 +67,7 @@ pub struct ProductionBootstrapPlan<'a> {
pub bundle_digest: BundleDigest,
pub invoking_uid: u32,
pub non_interactive_executor: Arc<dyn NonInteractiveSessionExecutor>,
pub(crate) runtime_staging: &'a crate::production::RuntimeStaging,
}
pub struct ProductionLifecycleAdapter<'a> {
@ -268,7 +269,7 @@ impl<'a> ProductionLifecycleAdapter<'a> {
&coordinator,
self.plan.prepared_artifact.prepared_artifact_id,
)
.map_err(failure)?;
.map_err(|error| failure(format!("mounting State Anchor: {error}")))?;
self.state_anchor_mount = Some(mount);
Ok(())
}
@ -281,7 +282,7 @@ impl<'a> ProductionLifecycleAdapter<'a> {
.ok_or_else(|| ProductionLifecycleError("Runtime Supervisor is absent".into()))?
.receive_helper_staged(&network)
.map(|_| ())
.map_err(failure)
.map_err(|error| failure(format!("starting connectivity helper: {error}")))
}
fn start_sidecar(&mut self) -> Result<(), ProductionLifecycleError> {
@ -289,7 +290,7 @@ impl<'a> ProductionLifecycleAdapter<'a> {
.as_mut()
.ok_or_else(|| ProductionLifecycleError("Runtime Supervisor is absent".into()))?
.start_sidecar()
.map_err(failure)
.map_err(|error| failure(format!("starting sidecar: {error}")))
}
fn mark_network_ready(&mut self) -> Result<(), ProductionLifecycleError> {
@ -309,7 +310,7 @@ impl<'a> ProductionLifecycleAdapter<'a> {
self.plan.reachability,
)
.map(|_| ())
.map_err(failure)
.map_err(|error| failure(format!("marking network ready: {error}")))
}
fn start_sandbox(&mut self) -> Result<(), ProductionLifecycleError> {
@ -319,17 +320,19 @@ impl<'a> ProductionLifecycleAdapter<'a> {
.awaiting_runtime
.take()
.ok_or_else(|| ProductionLifecycleError("Runtime Supervisor is absent".into()))?;
let staged = pending.receive_staged(&sandbox).map_err(failure)?;
let staged = pending
.receive_staged(&sandbox)
.map_err(|error| failure(format!("receiving attached runsc staging: {error}")))?;
let handoff = self
.plan
.sandbox_bundle
.capture_handoff_authority(self.plan.configuration)
.map_err(failure)?;
.map_err(|error| failure(format!("capturing sandbox handoff: {error}")))?;
let (runtime, _) = pending
.acknowledge(staged, &handoff)
.map_err(failure)?
.map_err(|error| failure(format!("releasing sandbox handoff: {error}")))?
.commit_networked(&sandbox)
.map_err(failure)?;
.map_err(|error| failure(format!("committing sandbox readiness: {error}")))?;
self.runtime = Some(runtime);
Ok(())
}
@ -376,7 +379,8 @@ impl<'a> ProductionLifecycleAdapter<'a> {
.as_mut()
.ok_or_else(|| ProductionLifecycleError("committed runtime is absent".into()))?
.stop_sandbox(&sandbox)
.map_err(failure)
.map_err(failure)?;
self.plan.runtime_staging.cleanup_overlay().map_err(failure)
}
fn stop_sidecar(&mut self) -> Result<(), ProductionLifecycleError> {
@ -408,7 +412,14 @@ impl<'a> ProductionLifecycleAdapter<'a> {
self.runtime
.as_mut()
.ok_or_else(|| ProductionLifecycleError("committed runtime is absent".into()))?
.unmount_state_anchor()
.unmount_state_anchor(
self.state_anchor_release
.as_ref()
.ok_or_else(|| {
ProductionLifecycleError("State Anchor release is absent".into())
})?
.mount(),
)
.map_err(failure)?;
self.state_anchor_mount = None;
Ok(())

View file

@ -68,6 +68,8 @@ pub struct ProductionColdStartInputs<'a> {
pub configuration: &'a ResolvedDomainConfiguration,
pub generation: Generation,
pub prepared_artifact: &'a PreparedArtifact,
pub prepared_artifact_authority: &'a File,
pub prepared_artifact_label: &'a Path,
pub root_process: RootProcess,
pub resolver_addresses: Vec<IpAddress>,
pub network_probe: NetworkProbeRequest,
@ -137,7 +139,7 @@ pub struct ProductionColdStart<'a> {
runtime_root: PathBuf,
bundle_digest: BundleDigest,
invoking_uid: u32,
_staging: RuntimeStaging,
staging: RuntimeStaging,
}
impl<'a> ProductionColdStart<'a> {
@ -173,6 +175,7 @@ impl<'a> ProductionColdStart<'a> {
expected_network_namespace,
root_process: inputs.root_process,
terminal: None,
prepared_root: inputs.prepared_artifact_label.to_owned(),
})
.map_err(|error| failure(format!("sandbox bundle: {error}")))?;
staging
@ -186,9 +189,13 @@ impl<'a> ProductionColdStart<'a> {
coordinator_id: inputs.generation.coordinator_id,
slot: HostProcessOwnershipSlot::SandboxRuntimeSupervisor,
};
let state_anchor =
StateAnchorLaunchAuthority::capture(&state_anchor_layout, inputs.prepared_artifact)
.map_err(|error| failure(format!("State Anchor authority: {error}")))?;
let state_anchor = StateAnchorLaunchAuthority::capture_retained(
&state_anchor_layout,
inputs.prepared_artifact,
inputs.prepared_artifact_label,
inputs.prepared_artifact_authority,
)
.map_err(|error| failure(format!("State Anchor authority: {error}")))?;
let runtime_launch = RuntimeSupervisorLaunch::from_networked_runsc_command(
ownership,
sandbox_bundle.command(),
@ -227,7 +234,7 @@ impl<'a> ProductionColdStart<'a> {
runtime_root: runtime_domain.join("runsc"),
bundle_digest,
invoking_uid: inputs.invoking_uid,
_staging: staging,
staging,
})
}
@ -342,6 +349,7 @@ impl<'a> ProductionColdStart<'a> {
)
.map_err(failure)?,
),
runtime_staging: &self.staging,
})
}
}
@ -362,17 +370,41 @@ fn require_inputs(inputs: &ProductionColdStartInputs<'_>) -> Result<(), Producti
Ok(())
}
struct RuntimeStaging {
pub(crate) struct RuntimeStaging {
bundles: OwnedFd,
bundle: OwnedFd,
controls: OwnedFd,
control: OwnedFd,
overlays: OwnedFd,
overlay: OwnedFd,
name: OsString,
bundle_identity: FileIdentity,
control_identity: FileIdentity,
overlay_identity: FileIdentity,
}
impl RuntimeStaging {
pub(crate) fn cleanup_overlay(&self) -> Result<(), ProductionColdStartError> {
let retained = fstat(&self.overlay).map_err(failure)?;
if !self.overlay_identity.matches(&retained) {
return Err(ProductionColdStartError(
"runsc overlay retained authority changed".into(),
));
}
remove_children(&self.overlay).map_err(failure)?;
let current =
statat(&self.overlays, &self.name, AtFlags::SYMLINK_NOFOLLOW).map_err(failure)?;
if !self.overlay_identity.matches(&current)
|| !FileType::from_raw_mode(current.st_mode).is_dir()
{
return Err(ProductionColdStartError(
"runsc overlay path changed before cleanup".into(),
));
}
unlinkat(&self.overlays, &self.name, AtFlags::REMOVEDIR).map_err(failure)?;
Ok(())
}
fn create(
runtime_domain: &ConfinedPathPlan,
container_id: ContainerId,
@ -384,6 +416,8 @@ impl RuntimeStaging {
let bundles = open_or_create_private_directory(&root, "bundles", invoking_uid, false)?;
let controls =
open_or_create_private_directory(&root, "sandbox-control", invoking_uid, false)?;
let overlays =
open_or_create_private_directory(&root, "runsc-overlay", invoking_uid, false)?;
drop(open_or_create_private_directory(
&root,
"runsc",
@ -401,14 +435,26 @@ impl RuntimeStaging {
}
};
let control_identity = FileIdentity::capture(&control)?;
let overlay = match open_or_create_private_directory(&overlays, &name, invoking_uid, true) {
Ok(overlay) => overlay,
Err(error) => {
remove_staging_directory(&controls, &name, &control, control_identity);
remove_staging_directory(&bundles, &name, &bundle, bundle_identity);
return Err(error);
}
};
let overlay_identity = FileIdentity::capture(&overlay)?;
let staging = Self {
bundles,
bundle,
controls,
control,
overlays,
overlay,
name,
bundle_identity,
control_identity,
overlay_identity,
};
staging.populate_control(escalation_client, sandbox_init)?;
Ok(staging)
@ -535,6 +581,12 @@ impl FileIdentity {
impl Drop for RuntimeStaging {
fn drop(&mut self) {
remove_staging_directory(
&self.overlays,
&self.name,
&self.overlay,
self.overlay_identity,
);
remove_staging_directory(
&self.controls,
&self.name,

View file

@ -1,8 +1,9 @@
use std::collections::BTreeSet;
use std::fmt;
use std::fs::File;
use std::os::unix::ffi::OsStringExt;
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use lazaret_core::configuration::ResolvedDomainConfiguration;
@ -11,14 +12,19 @@ use lazaret_core::identity::{
PrepareOperationId, SandboxId,
};
use lazaret_core::network::{DnsQueryExpectation, ReachabilityExpectation};
use lazaret_core::records::{DurableDecision, Generation, IpAddress, PreparedArtifact};
use lazaret_core::records::{DurableDecision, Generation, IpAddress, PreparedArtifact, VolumeKind};
use lazaret_linux::filesystem::namespace_identity;
use lazaret_linux::platform::{PlatformObservation, check_platform};
use lazaret_prepared_lower::{
IdMap, IdMapRange, OciRegistryTransport, Platform, PreparedLowerStore, RegistryCredentials,
IdMap, IdMapRange, OciRegistryTransport, Platform, PreparedDestination,
PreparedDestinationKind, PreparedLowerStore, RegistryCredentials,
};
use lazaret_runtime_supervisor::network::{DnsProbeTarget, NetworkProbeRequest, TcpProbeTarget};
use lazaret_runtime_supervisor::sandbox::{RootProcess, SANDBOX_INIT_DESTINATION};
use lazaret_runtime_supervisor::sandbox::{
CREDENTIALS_DESTINATION, ESCALATION_CLIENT_DESTINATION, MAILBOX_REQUESTS_DESTINATION,
MAILBOX_RESULTS_DESTINATION, RootProcess, SANDBOX_INIT_DESTINATION, TERMINFO_ROOT_DESTINATION,
WORKSPACE_DESTINATION,
};
use lazaret_storage_catalog::{Catalog, CatalogError};
use rustix::fs::{Mode, OFlags, ResolveFlags, openat2};
@ -54,6 +60,8 @@ pub struct ProductionSelection {
configuration_fingerprint: ConfigurationFingerprint,
catalog: Catalog,
artifact: PreparedArtifact,
artifact_authority: File,
artifact_label: PathBuf,
generation: Generation,
resolver_addresses: Vec<IpAddress>,
network_probe: NetworkProbeRequest,
@ -83,6 +91,8 @@ impl ProductionSelection {
configuration,
generation: self.generation.clone(),
prepared_artifact: &self.artifact,
prepared_artifact_authority: &self.artifact_authority,
prepared_artifact_label: &self.artifact_label,
root_process: RootProcess::new(
SANDBOX_INIT_DESTINATION,
[PRIVATE_SANDBOX_INIT_ROLE.to_owned()],
@ -149,14 +159,29 @@ async fn select_production_cold_start_for_platform(
random_id()?,
)
.await
.map_err(|error| failure(format!("pulling configured anonymous OCI image: {error}")))?;
.map_err(|error| failure(format!("pulling configured anonymous OCI image: {error:#}")))?;
let artifact = prepared
.prepare_transported(
PrepareOperationId::new(random_id()?),
&image,
rootless_id_map(),
prepared_destinations(configuration),
)
.map_err(failure)?;
let artifact_authority = prepared
.open_artifact_authority(&artifact)
.map_err(failure)?;
let artifact_label = PathBuf::from(std::ffi::OsString::from_vec(
configuration
.storage_root()
.canonical_label()
.as_bytes()
.to_vec(),
))
.join("prepared-lower")
.join(PathBuf::from(std::ffi::OsString::from_vec(
artifact.path.as_bytes().to_vec(),
)));
image.cleanup().map_err(failure)?;
let next_generation = next_generation(&store)?;
@ -179,6 +204,8 @@ async fn select_production_cold_start_for_platform(
configuration_fingerprint: configuration.configuration_fingerprint(),
catalog,
artifact,
artifact_authority,
artifact_label,
generation,
resolver_addresses,
network_probe,
@ -189,6 +216,66 @@ async fn select_production_cold_start_for_platform(
})
}
fn prepared_destinations(configuration: &ResolvedDomainConfiguration) -> Vec<PreparedDestination> {
let mut destinations = [
"/dev",
"/proc",
"/sys",
"/tmp",
"/etc",
"/home",
"/run",
WORKSPACE_DESTINATION,
CREDENTIALS_DESTINATION,
MAILBOX_REQUESTS_DESTINATION,
MAILBOX_RESULTS_DESTINATION,
TERMINFO_ROOT_DESTINATION,
]
.into_iter()
.map(|path| PreparedDestination {
path: path.into(),
kind: PreparedDestinationKind::Directory,
})
.chain(
[
"/etc/resolv.conf",
"/etc/hosts",
"/etc/hostname",
ESCALATION_CLIENT_DESTINATION,
SANDBOX_INIT_DESTINATION,
]
.into_iter()
.map(|path| PreparedDestination {
path: path.into(),
kind: PreparedDestinationKind::RegularFile,
}),
)
.collect::<Vec<_>>();
destinations.extend(
configuration
.fingerprint_record()
.persistent_dirs
.iter()
.map(|path| PreparedDestination {
path: path.into(),
kind: PreparedDestinationKind::Directory,
}),
);
destinations.extend(
configuration
.volumes()
.iter()
.map(|volume| PreparedDestination {
path: volume.record().container_path.as_str().into(),
kind: match volume.record().kind {
VolumeKind::Directory => PreparedDestinationKind::Directory,
VolumeKind::File => PreparedDestinationKind::RegularFile,
},
}),
);
destinations
}
fn next_generation(store: &HostCoordinatorDomainStore) -> Result<u64, ProductionSelectionError> {
store
.snapshot()
@ -380,6 +467,15 @@ mod tests {
id_map.gid[0].namespace_id,
rustix::process::getgid().as_raw()
);
let destinations = prepared_destinations(&configuration);
assert!(destinations.iter().any(|destination| {
destination.path == Path::new(WORKSPACE_DESTINATION)
&& destination.kind == PreparedDestinationKind::Directory
}));
assert!(destinations.iter().any(|destination| {
destination.path == Path::new(SANDBOX_INIT_DESTINATION)
&& destination.kind == PreparedDestinationKind::RegularFile
}));
}
#[test]

View file

@ -411,8 +411,13 @@ impl AwaitingAttachedRunsc {
.acknowledge_staged_daemon(staged)
.map_err(|error| SandboxCoordinatorError::Io(io::Error::other(error)))?;
control.acknowledge_state_anchor(acknowledgment)?;
let (ready_staged, mount) = match control.receive()? {
SupervisorEvent::StateAnchorReady { staged, mount } => (staged, mount),
let (ready_staged, user_namespace, mount_namespace, mount) = match control.receive()? {
SupervisorEvent::StateAnchorReady {
staged,
user_namespace,
mount_namespace,
mount,
} => (staged, user_namespace, mount_namespace, mount),
SupervisorEvent::Refused(detail) => {
return Err(SandboxCoordinatorError::Io(io::Error::other(detail)));
}
@ -430,7 +435,7 @@ impl AwaitingAttachedRunsc {
let readiness =
lazaret_state_anchor::VerifiedStateAnchorReadiness::from_parts(ready_staged, mount);
coordinator
.commit_daemon_ready(readiness)
.commit_daemon_ready(readiness, user_namespace, mount_namespace)
.and_then(|()| {
coordinator.commit_prepared(staged.generation_id(), prepared_artifact_id, mount)
})
@ -949,14 +954,16 @@ impl CommittedSandboxRuntime {
///
/// # Errors
/// Refuses an unexpected RS response or channel failure.
pub fn unmount_state_anchor(&mut self) -> Result<(), SandboxCoordinatorError> {
pub fn unmount_state_anchor(
&mut self,
expected_mount: MountIdentity,
) -> Result<(), SandboxCoordinatorError> {
self.control.unmount_state_anchor()?;
if self.control.receive()? == SupervisorEvent::StateAnchorUnmounted {
Ok(())
} else {
Err(SandboxCoordinatorError::InvalidState(
"Runtime Supervisor did not unmount the State Anchor",
))
match self.control.receive()? {
SupervisorEvent::StateAnchorUnmounted { mount } if mount == expected_mount => Ok(()),
_ => Err(SandboxCoordinatorError::InvalidState(
"Runtime Supervisor did not confirm the exact State Anchor unmount",
)),
}
}

View file

@ -3,15 +3,15 @@ use std::io;
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};
use lazaret_core::identity::{GenerationId, MountIdentity, PidfsProcessIdentityV1};
use lazaret_core::identity::{
GenerationId, MountIdentity, NamespaceIdentity, PidfsProcessIdentityV1,
};
use lazaret_core::records::{
ArtifactLease, HostProcessOwnershipSlot, Participant, ParticipantOwnership, ParticipantRole,
ParticipantState, PersistedHostProcessOwnership, PreparedArtifact, PreparedLowerOwnership,
ProcessOwnershipCommitAck, StagedProcessOwnership, StateAnchorOwnership,
};
use lazaret_state_anchor::{
StateAnchorError, StateAnchorLayout, VerifiedStateAnchorReadiness, require_mount_absent,
};
use lazaret_state_anchor::{StateAnchorError, StateAnchorLayout, VerifiedStateAnchorReadiness};
use lazaret_storage_catalog::{Catalog, CatalogError};
use crate::domain_store::{DomainStoreError, DomainStoreMutation, HostCoordinatorDomainStore};
@ -71,6 +71,8 @@ pub struct StateAnchorReleaseAuthority {
merged_label: PathBuf,
mount: MountIdentity,
daemon: PidfsProcessIdentityV1,
user_namespace: NamespaceIdentity,
mount_namespace: NamespaceIdentity,
}
impl StateAnchorReleaseAuthority {
@ -88,6 +90,16 @@ impl StateAnchorReleaseAuthority {
pub const fn daemon(&self) -> PidfsProcessIdentityV1 {
self.daemon
}
#[must_use]
pub const fn user_namespace(&self) -> NamespaceIdentity {
self.user_namespace
}
#[must_use]
pub const fn mount_namespace(&self) -> NamespaceIdentity {
self.mount_namespace
}
}
pub struct StateAnchorCoordinator<'a> {
@ -252,6 +264,8 @@ impl<'a> StateAnchorCoordinator<'a> {
pub fn commit_daemon_ready(
&self,
readiness: VerifiedStateAnchorReadiness,
user_namespace: NamespaceIdentity,
mount_namespace: NamespaceIdentity,
) -> Result<(), StateAnchorCoordinatorError> {
let staged = readiness.staged();
self.require_staged_identity(staged)?;
@ -262,6 +276,8 @@ impl<'a> StateAnchorCoordinator<'a> {
self.update_daemon_and_mount(
staged.generation_id(),
PersistedHostProcessOwnership::committed(staged.identity()),
Some(user_namespace),
Some(mount_namespace),
Some(readiness.mount()),
)
}
@ -365,7 +381,6 @@ impl<'a> StateAnchorCoordinator<'a> {
"release authority layout changed",
));
}
require_mount_absent(layout)?;
let anchor = self.participant(authority.generation_id, ParticipantRole::StateAnchor)?;
let prepared = self.participant(authority.generation_id, ParticipantRole::PreparedLower)?;
match (anchor.state(), prepared.state()) {
@ -392,7 +407,7 @@ impl<'a> StateAnchorCoordinator<'a> {
));
}
}
self.release_catalog_lease_if_present(authority, layout)?;
self.release_catalog_lease_if_present(authority)?;
self.domain_store
.apply(&[DomainStoreMutation::CleanParticipantAfterAbsence(
clean_participant(authority.generation_id, ParticipantRole::PreparedLower)?,
@ -426,13 +441,15 @@ impl<'a> StateAnchorCoordinator<'a> {
generation_id: GenerationId,
daemon: PersistedHostProcessOwnership,
) -> Result<(), StateAnchorCoordinatorError> {
self.update_daemon_and_mount(generation_id, daemon, None)
self.update_daemon_and_mount(generation_id, daemon, None, None, None)
}
fn update_daemon_and_mount(
&self,
generation_id: GenerationId,
daemon: PersistedHostProcessOwnership,
user_namespace: Option<NamespaceIdentity>,
mount_namespace: Option<NamespaceIdentity>,
mount: Option<MountIdentity>,
) -> Result<(), StateAnchorCoordinatorError> {
let current = self.participant(generation_id, ParticipantRole::StateAnchor)?;
@ -448,6 +465,8 @@ impl<'a> StateAnchorCoordinator<'a> {
}
let next = StateAnchorOwnership {
daemon,
user_namespace,
mount_namespace,
mount,
..ownership.clone()
};
@ -512,7 +531,6 @@ impl<'a> StateAnchorCoordinator<'a> {
fn release_catalog_lease_if_present(
&self,
authority: &StateAnchorReleaseAuthority,
layout: &StateAnchorLayout<'_>,
) -> Result<(), StateAnchorCoordinatorError> {
let snapshot = self.catalog.snapshot()?;
let generation_lease = snapshot.artifact_leases.iter().find(|lease| {
@ -552,7 +570,7 @@ impl<'a> StateAnchorCoordinator<'a> {
"Domain release evidence is not committed",
)));
}
require_mount_absent(layout).map_err(|error| CatalogError::Io(io::Error::other(error)))
Ok(())
})?;
Ok(())
}
@ -610,6 +628,12 @@ fn release_authority(
.daemon
.serviceable_pidfs()
.map_err(|_| StateAnchorCoordinatorError::InvalidState("daemon is not committed"))?,
user_namespace: anchor_ownership.user_namespace.ok_or(
StateAnchorCoordinatorError::InvalidState("user namespace identity is absent"),
)?,
mount_namespace: anchor_ownership.mount_namespace.ok_or(
StateAnchorCoordinatorError::InvalidState("mount namespace identity is absent"),
)?,
})
}

View file

@ -9,9 +9,10 @@ use lazaret_core::configuration::{
};
use lazaret_core::identity::{
BootId, ConfigurationFingerprint, CoordinatorId, DeviceId, GenerationId, HostPath,
HostPidLocator, IdMapDigest, InodeId, MountId, MountIdentity, NetworkSidecarId, PidfsDeviceId,
PidfsInodeId, PidfsProcessAuthorityV1, PidfsProcessIdentityV1, PolicyId, PrepareOperationId,
PreparedArtifactId, ProcessStartTimeTicks, SandboxId, SelectedManifestDigest, SourceIdentity,
HostPidLocator, IdMapDigest, InodeId, MountId, MountIdentity, NamespaceIdentity,
NetworkSidecarId, PidfsDeviceId, PidfsInodeId, PidfsProcessAuthorityV1, PidfsProcessIdentityV1,
PolicyId, PrepareOperationId, PreparedArtifactId, ProcessStartTimeTicks, SandboxId,
SelectedManifestDigest, SourceIdentity,
};
use lazaret_core::records::{
ArtifactState, Domain, DurableDecision, Generation, ParticipantRole, ParticipantState,
@ -138,6 +139,14 @@ fn durable_phases_resume_release_without_collapsing_lease_order() {
ParticipantState::Intent,
ParticipantOwnership::StateAnchor(StateAnchorOwnership {
daemon: PersistedHostProcessOwnership::committed(staged.identity()),
user_namespace: Some(NamespaceIdentity {
device: DeviceId::new(93),
inode: InodeId::new(94),
}),
mount_namespace: Some(NamespaceIdentity {
device: DeviceId::new(95),
inode: InodeId::new(96),
}),
mount: Some(MountIdentity {
mount_id: MountId::new(90),
device: DeviceId::new(91),

View file

@ -27,10 +27,13 @@ fn validated_inputs_stage_one_complete_plan_and_drop_exact_runtime_artifacts() {
let staged = ProductionColdStart::stage(fixture.inputs(&fixture.escalation_client)).unwrap();
let bundle = fixture.bundle();
let control = fixture.control();
let overlay = fixture.overlay();
assert_private_artifacts(&bundle, &control);
assert!(overlay.is_dir());
drop(staged);
assert!(!bundle.exists());
assert!(!control.exists());
assert!(!overlay.exists());
assert!(
ProductionColdStart::stage(fixture.inputs(&fixture.invalid_escalation_client)).is_err()
);
@ -90,6 +93,7 @@ struct ProductionFixture {
catalog: Catalog,
store: Arc<HostCoordinatorDomainStore>,
artifact: PreparedArtifact,
artifact_authority: File,
generation: Generation,
container_id: ContainerId,
escalation_client: File,
@ -139,6 +143,7 @@ impl ProductionFixture {
let lower = tree.path().join("prepared-lower");
fs::create_dir(&lower).unwrap();
let metadata = lower.metadata().unwrap();
let artifact_authority = File::open(&lower).unwrap();
let artifact = PreparedArtifact {
prepared_artifact_id: PreparedArtifactId::new([8; 32]),
source_identity: SourceIdentity::new(b"synthetic-spike".to_vec()),
@ -166,6 +171,7 @@ impl ProductionFixture {
catalog,
store,
artifact,
artifact_authority,
generation,
container_id: ContainerId::new([5; 32]),
escalation_client: File::open("/bin/true").unwrap(),
@ -180,6 +186,10 @@ impl ProductionFixture {
configuration: &self.configuration,
generation: self.generation.clone(),
prepared_artifact: &self.artifact,
prepared_artifact_authority: &self.artifact_authority,
prepared_artifact_label: Path::new(
std::str::from_utf8(self.artifact.path.as_bytes()).unwrap(),
),
root_process: RootProcess::new("/bin/sh", ["-c".into(), "sleep infinity".into()])
.unwrap(),
resolver_addresses: vec![IpAddress::V4([10, 0, 2, 3])],
@ -214,6 +224,12 @@ impl ProductionFixture {
.join("sandbox-control")
.join(hex(self.container_id.as_bytes()))
}
fn overlay(&self) -> PathBuf {
self.runtime_domain
.join("runsc-overlay")
.join(hex(self.container_id.as_bytes()))
}
}
fn assert_private_artifacts(bundle: &Path, control: &Path) {

View file

@ -686,6 +686,7 @@ impl Fixture {
expected_network_namespace: namespace,
root_process,
terminal: None,
prepared_root: runtime_domain.join("prepared-root"),
})
.map_err(failure)?;
Self::finish_build(
@ -743,6 +744,7 @@ impl Fixture {
expected_network_namespace: namespace,
root_process,
terminal: None,
prepared_root: runtime_domain.join("prepared-root"),
})
.map_err(failure)?;
Self::finish_build(
@ -846,6 +848,8 @@ impl Fixture {
)
.map_err(failure)?,
terminal: None,
prepared_root: host_path(self.configuration.runtime_domain_root().canonical_label())
.join("prepared-root"),
})
.map_err(failure)
}
@ -888,7 +892,7 @@ impl NetworkFixture {
let runtime_domain = host_path(configuration.runtime_domain_root().canonical_label());
let container_id = ContainerId::new([16; 32]);
stage_rootfs_and_control(&runtime_domain, container_id)?;
let rootfs = runtime_domain.join("state-anchor/merged");
let rootfs = runtime_domain.join("prepared-root");
fs::copy(
std::env::current_exe()?,
rootfs.join("bin/lazaret-network-probe"),
@ -917,6 +921,7 @@ impl NetworkFixture {
expected_network_namespace: namespace,
root_process,
terminal: None,
prepared_root: runtime_domain.join("prepared-root"),
})
.map_err(failure)?;
let bundle_path = runtime_domain
@ -1177,10 +1182,44 @@ fn stage_rootfs_and_control(
runtime_domain: &Path,
container_id: ContainerId,
) -> Result<(), MatrixError> {
let rootfs = runtime_domain.join("state-anchor/merged");
for directory in ["bin", "lib64"] {
let rootfs = runtime_domain.join("prepared-root");
for directory in [
"bin",
"lib64",
"dev",
"proc",
"sys",
"tmp",
"etc",
"home",
"workspace",
"run/lazaret/credentials",
"run/lazaret/mailbox/requests",
"run/lazaret/mailbox/results",
"run/lazaret/bin",
] {
fs::create_dir_all(rootfs.join(directory))?;
}
for file in [
"etc/resolv.conf",
"etc/hosts",
"etc/hostname",
"run/lazaret/bin/lazaret-escalate",
"run/lazaret/bin/lazaret-sandbox-init",
] {
fs::write(rootfs.join(file), b"")?;
}
fs::create_dir_all(runtime_domain.join("state-anchor/merged"))?;
let runtime_files = runtime_domain.join("state-anchor/runtime-files");
fs::create_dir_all(&runtime_files)?;
for file in ["resolv.conf", "hosts", "hostname"] {
fs::write(runtime_files.join(file), b"fixture")?;
}
fs::create_dir_all(
runtime_domain
.join("runsc-overlay")
.join(hex(container_id.as_bytes())),
)?;
for (source, destination) in [
("/bin/sh", "bin/sh"),
("/bin/sleep", "bin/sleep"),
@ -1199,11 +1238,12 @@ fn stage_rootfs_and_control(
fs::create_dir_all(control.join(directory))?;
}
fs::copy("/bin/true", control.join("bin/lazaret-escalate"))?;
fs::copy("/bin/true", control.join("bin/lazaret-sandbox-init"))?;
Ok(())
}
fn stage_benchmark_rootfs_and_control(runtime_domain: &Path) -> Result<(), MatrixError> {
let rootfs = runtime_domain.join("state-anchor/merged");
let rootfs = runtime_domain.join("prepared-root");
for directory in [
"bin",
"tooling/usr",
@ -1213,6 +1253,17 @@ fn stage_benchmark_rootfs_and_control(runtime_domain: &Path) -> Result<(), Matri
"fixtures/host",
"fixtures/output",
"fixtures/repository",
"dev",
"proc",
"sys",
"tmp",
"etc",
"home",
"workspace",
"run/lazaret/credentials",
"run/lazaret/mailbox/requests",
"run/lazaret/mailbox/results",
"run/lazaret/bin",
] {
fs::create_dir_all(rootfs.join(directory))?;
}
@ -1224,12 +1275,31 @@ fn stage_benchmark_rootfs_and_control(runtime_domain: &Path) -> Result<(), Matri
symlink("usr/lib", rootfs.join("lib"))?;
symlink("usr/lib64", rootfs.join("lib64"))?;
symlink("usr/sbin", rootfs.join("sbin"))?;
for file in [
"etc/resolv.conf",
"etc/hosts",
"etc/hostname",
"run/lazaret/bin/lazaret-escalate",
"run/lazaret/bin/lazaret-sandbox-init",
] {
fs::write(rootfs.join(file), b"")?;
}
for directory in ["tooling", "fixtures"] {
fs::create_dir_all(runtime_domain.join("state-anchor/merged").join(directory))?;
}
let runtime_files = runtime_domain.join("state-anchor/runtime-files");
fs::create_dir_all(&runtime_files)?;
for file in ["resolv.conf", "hosts", "hostname"] {
fs::write(runtime_files.join(file), b"fixture")?;
}
fs::create_dir_all(runtime_domain.join("runsc-overlay").join(hex(&[6; 32])))?;
let control = runtime_domain.join("sandbox-control").join(hex(&[6; 32]));
for directory in ["credentials", "mailbox/requests", "mailbox/results", "bin"] {
fs::create_dir_all(control.join(directory))?;
}
fs::copy("/bin/true", control.join("bin/lazaret-escalate"))?;
fs::copy("/bin/true", control.join("bin/lazaret-sandbox-init"))?;
Ok(())
}