Route production control through lifecycle ownership
This commit is contained in:
parent
33ac0ba4ee
commit
da7557b850
2 changed files with 260 additions and 20 deletions
|
|
@ -1,6 +1,7 @@
|
|||
use std::fmt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use lazaret_core::configuration::ResolvedDomainConfiguration;
|
||||
use lazaret_core::identity::{BundleDigest, ContainerId, MountIdentity};
|
||||
|
|
@ -15,7 +16,9 @@ use lazaret_state_anchor::StateAnchorLayout;
|
|||
use lazaret_storage_catalog::Catalog;
|
||||
|
||||
use crate::control::ControlSessionAdmission;
|
||||
use crate::control::{PublishedControlFace, SessionRegistry, StagedControlFace, TrustAnchor};
|
||||
use crate::control::{
|
||||
ControlError, PublishedControlFace, SessionRegistry, StagedControlFace, TrustAnchor,
|
||||
};
|
||||
use crate::domain_store::HostCoordinatorDomainStore;
|
||||
use crate::lifecycle::{
|
||||
HostCoordinatorLifecycle, LifecycleAction, LifecycleAdapter, SessionAdmissionIdentity,
|
||||
|
|
@ -63,7 +66,7 @@ pub struct ProductionLifecycleAdapter<'a> {
|
|||
registry: Arc<SessionRegistry>,
|
||||
trust_anchor: Option<TrustAnchor>,
|
||||
staged_control: Option<StagedControlFace>,
|
||||
published_control: Option<PublishedControlFace>,
|
||||
published_control: Option<Arc<PublishedControlFace>>,
|
||||
awaiting_runtime: Option<AwaitingAttachedRunsc>,
|
||||
runtime: Option<CommittedSandboxRuntime>,
|
||||
state_anchor_mount: Option<MountIdentity>,
|
||||
|
|
@ -169,8 +172,8 @@ impl<'a> ProductionLifecycleAdapter<'a> {
|
|||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn published_control(&self) -> Option<&PublishedControlFace> {
|
||||
self.published_control.as_ref()
|
||||
pub fn published_control(&self) -> Option<Arc<PublishedControlFace>> {
|
||||
self.published_control.as_ref().map(Arc::clone)
|
||||
}
|
||||
|
||||
fn require_generation(&self, generation: &Generation) -> Result<(), ProductionLifecycleError> {
|
||||
|
|
@ -347,13 +350,13 @@ impl<'a> ProductionLifecycleAdapter<'a> {
|
|||
}
|
||||
|
||||
fn expose_publication(&mut self) -> Result<(), ProductionLifecycleError> {
|
||||
self.published_control = Some(
|
||||
self.published_control = Some(Arc::new(
|
||||
self.staged_control
|
||||
.take()
|
||||
.ok_or_else(|| ProductionLifecycleError("staged control face is absent".into()))?
|
||||
.expose()
|
||||
.map_err(failure)?,
|
||||
);
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -445,6 +448,12 @@ impl LifecycleAdapter for ProductionLifecycleAdapter<'_> {
|
|||
LifecycleAction::UnmountStateAnchor => self.unmount_state_anchor(),
|
||||
LifecycleAction::ReleasePreparedLowerLease => self.release_prepared_lower(),
|
||||
LifecycleAction::GracefulSupervisorExit => self.graceful_exit(),
|
||||
LifecycleAction::BeginTeardown => {
|
||||
if let Some(control) = self.published_control.take() {
|
||||
control.withdraw();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
LifecycleAction::FinishTeardown => {
|
||||
self.published_control.take();
|
||||
Ok(())
|
||||
|
|
@ -457,8 +466,7 @@ impl LifecycleAdapter for ProductionLifecycleAdapter<'_> {
|
|||
| LifecycleAction::AdvanceWaitingSession
|
||||
| LifecycleAction::RejectConfigurationMismatch
|
||||
| LifecycleAction::RegisterSession
|
||||
| LifecycleAction::DisconnectSession
|
||||
| LifecycleAction::BeginTeardown => Ok(()),
|
||||
| LifecycleAction::DisconnectSession => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -476,6 +484,37 @@ impl LifecycleAdapter for ProductionLifecycleAdapter<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Serves one production control connection through the exact retained lifecycle owner.
|
||||
///
|
||||
/// # Errors
|
||||
/// Refuses absent publication, poisoned lifecycle ownership, or control/session failure.
|
||||
pub fn serve_production_control(
|
||||
lifecycle: &Mutex<ProductionHostCoordinatorLifecycle<'_, '_>>,
|
||||
) -> Result<(), ControlError> {
|
||||
let face = lifecycle
|
||||
.lock()
|
||||
.map_err(|_| ControlError::LockPoisoned)?
|
||||
.adapter()
|
||||
.published_control()
|
||||
.ok_or(ControlError::StaleGeneration)?;
|
||||
face.serve_next_coordinated(
|
||||
|admission| {
|
||||
lifecycle
|
||||
.lock()
|
||||
.map_err(|_| ControlError::LockPoisoned)?
|
||||
.admit_session(admission)
|
||||
.map_err(|error| ControlError::Lifecycle(error.to_string()))
|
||||
},
|
||||
|session_id| {
|
||||
lifecycle
|
||||
.lock()
|
||||
.map_err(|_| ControlError::LockPoisoned)?
|
||||
.disconnect_session(session_id)
|
||||
.map_err(|error| ControlError::Lifecycle(error.to_string()))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn path_from_host(bytes: &[u8]) -> &Path {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ pub enum ControlError {
|
|||
InvalidRequest,
|
||||
OversizeRequest { length: usize, maximum: usize },
|
||||
LockPoisoned,
|
||||
Lifecycle(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for ControlError {
|
||||
|
|
@ -100,6 +101,7 @@ impl fmt::Display for ControlError {
|
|||
)
|
||||
}
|
||||
Self::LockPoisoned => formatter.write_str("control registry lock is poisoned"),
|
||||
Self::Lifecycle(detail) => write!(formatter, "control lifecycle failed: {detail}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1007,12 +1009,42 @@ impl PublishedControlFace {
|
|||
&self.paths.identity
|
||||
}
|
||||
|
||||
/// Withdraws exact discovery and connection paths while retaining live accepted streams.
|
||||
pub fn withdraw(&self) {
|
||||
remove_if_identity_matches(&self.paths.socket, self.socket_identity);
|
||||
remove_if_identity_matches(&self.paths.identity, self.identity_file_identity);
|
||||
}
|
||||
|
||||
/// Accepts and serves one TLS connection. Registered connections remain live until EOF.
|
||||
///
|
||||
/// # Errors
|
||||
/// Refuses peer UID, publication, TLS, ALPN, certificate, framing, tuple, duplicate, and
|
||||
/// persistence failures.
|
||||
pub fn serve_next(&self) -> Result<(), ControlError> {
|
||||
self.serve_next_coordinated(
|
||||
|admission| {
|
||||
self.registry
|
||||
.register(admission.request, &admission.credential)
|
||||
.map(|_| ())
|
||||
},
|
||||
|session_id| self.registry.disconnect(session_id),
|
||||
)
|
||||
}
|
||||
|
||||
/// Serves one connection while routing authenticated session state through its lifecycle.
|
||||
///
|
||||
/// # Errors
|
||||
/// Refuses the same conditions as [`Self::serve_next`] plus lifecycle admission or disconnect
|
||||
/// failure.
|
||||
pub fn serve_next_coordinated<Admit, Disconnect>(
|
||||
&self,
|
||||
mut admit: Admit,
|
||||
mut disconnect: Disconnect,
|
||||
) -> Result<(), ControlError>
|
||||
where
|
||||
Admit: FnMut(ControlSessionAdmission) -> Result<(), ControlError>,
|
||||
Disconnect: FnMut(SessionId) -> Result<(), ControlError>,
|
||||
{
|
||||
let (socket, _) = self.listener.accept()?;
|
||||
let peer = socket_peercred(&socket).map_err(io::Error::from)?;
|
||||
require_peer_uid(peer.uid.as_raw(), self.uid)?;
|
||||
|
|
@ -1034,7 +1066,9 @@ impl PublishedControlFace {
|
|||
let mut stream = rustls::StreamOwned::new(connection, socket);
|
||||
match peer_certificate {
|
||||
None => self.serve_enrollment(&mut stream),
|
||||
Some(certificates) => self.serve_registration(&mut stream, &certificates),
|
||||
Some(certificates) => {
|
||||
self.serve_registration(&mut stream, &certificates, &mut admit, &mut disconnect)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1057,11 +1091,17 @@ impl PublishedControlFace {
|
|||
)
|
||||
}
|
||||
|
||||
fn serve_registration(
|
||||
fn serve_registration<Admit, Disconnect>(
|
||||
&self,
|
||||
stream: &mut rustls::StreamOwned<ServerConnection, std::os::unix::net::UnixStream>,
|
||||
certificates: &[Vec<u8>],
|
||||
) -> Result<(), ControlError> {
|
||||
admit: &mut Admit,
|
||||
disconnect: &mut Disconnect,
|
||||
) -> Result<(), ControlError>
|
||||
where
|
||||
Admit: FnMut(ControlSessionAdmission) -> Result<(), ControlError>,
|
||||
Disconnect: FnMut(SessionId) -> Result<(), ControlError>,
|
||||
{
|
||||
let [leaf] = certificates else {
|
||||
return Err(ControlError::InvalidCertificateProfile);
|
||||
};
|
||||
|
|
@ -1076,7 +1116,11 @@ impl PublishedControlFace {
|
|||
self.anchor.sandbox_protocol_credential.credential_id,
|
||||
&self.anchor.coordinator_protocol_key,
|
||||
)?;
|
||||
let registered = self.registry.register(request, &credential)?;
|
||||
let session_id = request.session_binding.session_id;
|
||||
admit(ControlSessionAdmission {
|
||||
request,
|
||||
credential,
|
||||
})?;
|
||||
let result = (|| {
|
||||
write_frame(
|
||||
stream,
|
||||
|
|
@ -1093,8 +1137,8 @@ impl PublishedControlFace {
|
|||
Err(error) => Err(error.into()),
|
||||
}
|
||||
})();
|
||||
let disconnect = self.registry.disconnect(registered.binding.session_id);
|
||||
result.and(disconnect)
|
||||
let disconnected = disconnect(session_id);
|
||||
result.and(disconnected)
|
||||
}
|
||||
|
||||
fn validate_publication(&self) -> Result<(), ControlError> {
|
||||
|
|
@ -1216,8 +1260,7 @@ impl PublishedControlFace {
|
|||
|
||||
impl Drop for PublishedControlFace {
|
||||
fn drop(&mut self) {
|
||||
remove_if_identity_matches(&self.paths.socket, self.socket_identity);
|
||||
remove_if_identity_matches(&self.paths.identity, self.identity_file_identity);
|
||||
self.withdraw();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue