Require fresh State Anchor absence before lease release
This commit is contained in:
parent
4dd2b150d2
commit
3b76b59bdf
1 changed files with 543 additions and 194 deletions
|
|
@ -9,7 +9,7 @@ use std::time::{Duration, Instant};
|
|||
use lazaret_core::codec::{decode, encode};
|
||||
use lazaret_core::identity::{
|
||||
ConfigurationFingerprint, CoordinatorId, DerivedPolicyDigest, DomainId, GenerationId,
|
||||
NetworkSidecarId,
|
||||
MountIdentity, NamespaceIdentity, NetworkSidecarId,
|
||||
};
|
||||
use lazaret_core::network::CanonicalDerivedNetworkPolicy;
|
||||
use lazaret_core::records::{
|
||||
|
|
@ -23,7 +23,8 @@ use lazaret_linux::process::{
|
|||
child_broker, current_process_identity,
|
||||
};
|
||||
use lazaret_state_anchor::{
|
||||
RuntimeStateAnchorAuthority, RuntimeStateAnchorMount, StateAnchorLaunchAuthority,
|
||||
RuntimeStateAnchorAbsenceAuthority, RuntimeStateAnchorAuthority, RuntimeStateAnchorInherited,
|
||||
RuntimeStateAnchorMount, StateAnchorLaunchAuthority,
|
||||
};
|
||||
use rustix::event::{PollFd, PollFlags, Timespec, poll};
|
||||
use rustix::fs::{MemfdFlags, memfd_create};
|
||||
|
|
@ -86,6 +87,12 @@ pub enum SupervisorEvent {
|
|||
StateAnchorUnmounted {
|
||||
mount: lazaret_core::identity::MountIdentity,
|
||||
},
|
||||
StateAnchorAbsenceObserved {
|
||||
user_namespace: NamespaceIdentity,
|
||||
mount_namespace: NamespaceIdentity,
|
||||
mount: MountIdentity,
|
||||
observed: MountIdentity,
|
||||
},
|
||||
Exited,
|
||||
AttachedRunscExited,
|
||||
Stopped,
|
||||
|
|
@ -106,6 +113,11 @@ enum CoordinatorMessage {
|
|||
StopSandbox,
|
||||
StopSidecar,
|
||||
UnmountStateAnchor,
|
||||
ObserveStateAnchorAbsence {
|
||||
user_namespace: NamespaceIdentity,
|
||||
mount_namespace: NamespaceIdentity,
|
||||
mount: MountIdentity,
|
||||
},
|
||||
Exit,
|
||||
Stop,
|
||||
}
|
||||
|
|
@ -225,6 +237,24 @@ impl CoordinatorChannel {
|
|||
send_message(&self.descriptor, &CoordinatorMessage::UnmountStateAnchor)
|
||||
}
|
||||
|
||||
/// # Errors
|
||||
/// Returns an error when exact namespace-local absence observation cannot be requested.
|
||||
pub fn observe_state_anchor_absence(
|
||||
&self,
|
||||
user_namespace: NamespaceIdentity,
|
||||
mount_namespace: NamespaceIdentity,
|
||||
mount: MountIdentity,
|
||||
) -> io::Result<()> {
|
||||
send_message(
|
||||
&self.descriptor,
|
||||
&CoordinatorMessage::ObserveStateAnchorAbsence {
|
||||
user_namespace,
|
||||
mount_namespace,
|
||||
mount,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// # Errors
|
||||
/// Returns an error when graceful Runtime Supervisor exit cannot be requested.
|
||||
pub fn exit(&self) -> io::Result<()> {
|
||||
|
|
@ -657,8 +687,19 @@ fn run_supervisor(arguments: &RuntimeArguments) -> io::Result<()> {
|
|||
let control = std::io::stdout();
|
||||
let parent = current_process_identity()?;
|
||||
let mut state_anchor = prepare_state_anchor(arguments, parent, namespaces, &control)?;
|
||||
let result = run_supervisor_with_state(arguments, parent, &control, &mut state_anchor);
|
||||
let cleanup = state_anchor.take().map(RuntimeStateAnchorMount::unmount);
|
||||
let mut state_anchor_absence = None;
|
||||
let result = run_supervisor_with_state(
|
||||
arguments,
|
||||
parent,
|
||||
&control,
|
||||
namespaces,
|
||||
&mut state_anchor,
|
||||
&mut state_anchor_absence,
|
||||
);
|
||||
let cleanup = state_anchor
|
||||
.take()
|
||||
.map(RuntimeStateAnchorMount::unmount)
|
||||
.map(|result| result.map(drop));
|
||||
match (result, cleanup) {
|
||||
(Err(error), Some(Err(cleanup))) => Err(io::Error::new(
|
||||
error.kind(),
|
||||
|
|
@ -688,17 +729,17 @@ fn prepare_state_anchor(
|
|||
"Runtime Supervisor received no State Anchor mount command",
|
||||
));
|
||||
}
|
||||
let authority = RuntimeStateAnchorAuthority::from_inherited(
|
||||
state.lower_fd,
|
||||
&state.lower_label,
|
||||
state.persistent_root_fd,
|
||||
state.persistent_anchor_fd,
|
||||
&state.persistent_anchor_label,
|
||||
state.runtime_root_fd,
|
||||
state.runtime_anchor_fd,
|
||||
&state.runtime_anchor_label,
|
||||
state.expected_lower,
|
||||
)
|
||||
let authority = RuntimeStateAnchorAuthority::from_inherited(RuntimeStateAnchorInherited {
|
||||
lower_fd: state.lower_fd,
|
||||
lower_label: &state.lower_label,
|
||||
persistent_root_fd: state.persistent_root_fd,
|
||||
persistent_anchor_fd: state.persistent_anchor_fd,
|
||||
persistent_anchor_label: &state.persistent_anchor_label,
|
||||
runtime_root_fd: state.runtime_root_fd,
|
||||
runtime_anchor_fd: state.runtime_anchor_fd,
|
||||
runtime_anchor_label: &state.runtime_anchor_label,
|
||||
expected_lower: state.expected_lower,
|
||||
})
|
||||
.map_err(io::Error::other)?;
|
||||
let ownership = ProcessOwnershipContext {
|
||||
slot: HostProcessOwnershipSlot::StateAnchorDaemon,
|
||||
|
|
@ -742,7 +783,9 @@ fn run_supervisor_with_state(
|
|||
arguments: &RuntimeArguments,
|
||||
parent: lazaret_core::identity::PidfsProcessIdentityV1,
|
||||
control: &std::io::Stdout,
|
||||
namespaces: (NamespaceIdentity, NamespaceIdentity),
|
||||
state_anchor: &mut Option<RuntimeStateAnchorMount>,
|
||||
state_anchor_absence: &mut Option<RuntimeStateAnchorAbsenceAuthority>,
|
||||
) -> io::Result<()> {
|
||||
if arguments.state_anchor.is_some() {
|
||||
let message: CoordinatorMessage = receive_message(control)?;
|
||||
|
|
@ -827,7 +870,15 @@ fn run_supervisor_with_state(
|
|||
io::Error::other("sandbox commit acknowledgment was not received"),
|
||||
);
|
||||
}
|
||||
supervise(control, process, network, state_anchor, &delete)
|
||||
supervise(
|
||||
control,
|
||||
process,
|
||||
network,
|
||||
namespaces,
|
||||
state_anchor,
|
||||
state_anchor_absence,
|
||||
&delete,
|
||||
)
|
||||
}
|
||||
|
||||
fn prepare_runtime_network(
|
||||
|
|
@ -983,10 +1034,17 @@ fn supervise(
|
|||
control: &std::io::Stdout,
|
||||
process: RetainedProcess,
|
||||
mut network: Option<PreparedNetwork>,
|
||||
namespaces: (NamespaceIdentity, NamespaceIdentity),
|
||||
state_anchor: &mut Option<RuntimeStateAnchorMount>,
|
||||
state_anchor_absence: &mut Option<RuntimeStateAnchorAbsenceAuthority>,
|
||||
delete: &TargetCommand,
|
||||
) -> io::Result<()> {
|
||||
let mut process = Some(process);
|
||||
let mut state_anchor = StateAnchorSupervision {
|
||||
namespaces,
|
||||
mount: state_anchor,
|
||||
absence: state_anchor_absence,
|
||||
};
|
||||
loop {
|
||||
let exited = match process
|
||||
.as_ref()
|
||||
|
|
@ -1059,7 +1117,7 @@ fn supervise(
|
|||
&message,
|
||||
&mut process,
|
||||
&mut network,
|
||||
state_anchor,
|
||||
&mut state_anchor,
|
||||
delete,
|
||||
) {
|
||||
Ok(true) => return Ok(()),
|
||||
|
|
@ -1070,12 +1128,18 @@ fn supervise(
|
|||
}
|
||||
}
|
||||
|
||||
struct StateAnchorSupervision<'a> {
|
||||
namespaces: (NamespaceIdentity, NamespaceIdentity),
|
||||
mount: &'a mut Option<RuntimeStateAnchorMount>,
|
||||
absence: &'a mut Option<RuntimeStateAnchorAbsenceAuthority>,
|
||||
}
|
||||
|
||||
fn handle_supervisor_message(
|
||||
control: &std::io::Stdout,
|
||||
message: &CoordinatorMessage,
|
||||
process: &mut Option<RetainedProcess>,
|
||||
network: &mut Option<PreparedNetwork>,
|
||||
state_anchor: &mut Option<RuntimeStateAnchorMount>,
|
||||
state_anchor: &mut StateAnchorSupervision<'_>,
|
||||
delete: &TargetCommand,
|
||||
) -> io::Result<bool> {
|
||||
match message {
|
||||
|
|
@ -1102,25 +1166,17 @@ fn handle_supervisor_message(
|
|||
stop_network(network.take())?;
|
||||
send_message(control, &SupervisorEvent::SidecarStopped)?;
|
||||
}
|
||||
CoordinatorMessage::UnmountStateAnchor => {
|
||||
if process.is_some() || network.is_some() {
|
||||
return Err(io::Error::other(
|
||||
"State Anchor unmount preceded runtime resource stop",
|
||||
));
|
||||
}
|
||||
let mount = state_anchor
|
||||
.as_ref()
|
||||
.ok_or_else(|| io::Error::other("State Anchor is absent"))?
|
||||
.mount_identity();
|
||||
state_anchor
|
||||
.take()
|
||||
.ok_or_else(|| io::Error::other("State Anchor is absent"))?
|
||||
.unmount()
|
||||
.map_err(io::Error::other)?;
|
||||
send_message(control, &SupervisorEvent::StateAnchorUnmounted { mount })?;
|
||||
CoordinatorMessage::UnmountStateAnchor
|
||||
| CoordinatorMessage::ObserveStateAnchorAbsence { .. } => {
|
||||
handle_state_anchor_message(
|
||||
control,
|
||||
message,
|
||||
process.is_some() || network.is_some(),
|
||||
state_anchor,
|
||||
)?;
|
||||
}
|
||||
CoordinatorMessage::Exit => {
|
||||
if process.is_some() || network.is_some() || state_anchor.is_some() {
|
||||
if process.is_some() || network.is_some() || state_anchor.mount.is_some() {
|
||||
return Err(io::Error::other(
|
||||
"Runtime Supervisor exit preceded resource cleanup",
|
||||
));
|
||||
|
|
@ -1150,6 +1206,70 @@ fn handle_supervisor_message(
|
|||
Ok(false)
|
||||
}
|
||||
|
||||
fn handle_state_anchor_message(
|
||||
control: &std::io::Stdout,
|
||||
message: &CoordinatorMessage,
|
||||
runtime_resources_present: bool,
|
||||
state: &mut StateAnchorSupervision<'_>,
|
||||
) -> io::Result<()> {
|
||||
match message {
|
||||
CoordinatorMessage::UnmountStateAnchor => {
|
||||
if runtime_resources_present {
|
||||
return Err(io::Error::other(
|
||||
"State Anchor unmount preceded runtime resource stop",
|
||||
));
|
||||
}
|
||||
let mount = state
|
||||
.mount
|
||||
.as_ref()
|
||||
.ok_or_else(|| io::Error::other("State Anchor is absent"))?
|
||||
.mount_identity();
|
||||
let absence = state
|
||||
.mount
|
||||
.take()
|
||||
.ok_or_else(|| io::Error::other("State Anchor is absent"))?
|
||||
.unmount()
|
||||
.map_err(io::Error::other)?;
|
||||
*state.absence = Some(absence);
|
||||
send_message(control, &SupervisorEvent::StateAnchorUnmounted { mount })
|
||||
}
|
||||
CoordinatorMessage::ObserveStateAnchorAbsence {
|
||||
user_namespace,
|
||||
mount_namespace,
|
||||
mount,
|
||||
} => {
|
||||
if (*user_namespace, *mount_namespace) != state.namespaces {
|
||||
return send_message(
|
||||
control,
|
||||
&SupervisorEvent::Refused(
|
||||
"State Anchor absence request namespace authority mismatched".into(),
|
||||
),
|
||||
);
|
||||
}
|
||||
match state.absence.as_ref().map_or_else(
|
||||
|| {
|
||||
Err(lazaret_state_anchor::StateAnchorError::InvalidLayout(
|
||||
"State Anchor absence authority is absent",
|
||||
))
|
||||
},
|
||||
|authority| authority.observe_absence(*mount),
|
||||
) {
|
||||
Ok(observed) => send_message(
|
||||
control,
|
||||
&SupervisorEvent::StateAnchorAbsenceObserved {
|
||||
user_namespace: state.namespaces.0,
|
||||
mount_namespace: state.namespaces.1,
|
||||
mount: *mount,
|
||||
observed,
|
||||
},
|
||||
),
|
||||
Err(error) => send_message(control, &SupervisorEvent::Refused(error.to_string())),
|
||||
}
|
||||
}
|
||||
_ => Err(io::Error::other("message is not a State Anchor command")),
|
||||
}
|
||||
}
|
||||
|
||||
fn fail_active_runtime(
|
||||
process: Option<RetainedProcess>,
|
||||
network: Option<PreparedNetwork>,
|
||||
|
|
@ -1347,6 +1467,30 @@ mod tests {
|
|||
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
|
||||
CoordinatorMessage::UnmountStateAnchor
|
||||
);
|
||||
let user_namespace = NamespaceIdentity {
|
||||
device: lazaret_core::identity::DeviceId::new(11),
|
||||
inode: lazaret_core::identity::InodeId::new(12),
|
||||
};
|
||||
let mount_namespace = NamespaceIdentity {
|
||||
device: lazaret_core::identity::DeviceId::new(13),
|
||||
inode: lazaret_core::identity::InodeId::new(14),
|
||||
};
|
||||
let mount = MountIdentity {
|
||||
mount_id: lazaret_core::identity::MountId::new(15),
|
||||
device: lazaret_core::identity::DeviceId::new(16),
|
||||
inode: lazaret_core::identity::InodeId::new(17),
|
||||
};
|
||||
channel
|
||||
.observe_state_anchor_absence(user_namespace, mount_namespace, mount)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
|
||||
CoordinatorMessage::ObserveStateAnchorAbsence {
|
||||
user_namespace,
|
||||
mount_namespace,
|
||||
mount,
|
||||
}
|
||||
);
|
||||
channel.exit().unwrap();
|
||||
assert_eq!(
|
||||
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
|
||||
|
|
|
|||
Loading…
Reference in a new issue