Compose selected State Anchor paths over prepared roots
This commit is contained in:
parent
71b0266cf2
commit
4dd2b150d2
6 changed files with 1575 additions and 163 deletions
|
|
@ -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(())
|
||||
|
|
|
|||
|
|
@ -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(¤t)
|
||||
|| !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,
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
)?,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
Loading…
Reference in a new issue