Fail teardown when control withdrawal fails

This commit is contained in:
ms2k 2026-07-16 17:53:54 +09:00
commit a9c9cf7ab3
3 changed files with 51 additions and 11 deletions

View file

@ -449,9 +449,10 @@ impl LifecycleAdapter for ProductionLifecycleAdapter<'_> {
LifecycleAction::ReleasePreparedLowerLease => self.release_prepared_lower(), LifecycleAction::ReleasePreparedLowerLease => self.release_prepared_lower(),
LifecycleAction::GracefulSupervisorExit => self.graceful_exit(), LifecycleAction::GracefulSupervisorExit => self.graceful_exit(),
LifecycleAction::BeginTeardown => { LifecycleAction::BeginTeardown => {
if let Some(control) = self.published_control.take() { if let Some(control) = self.published_control.as_ref() {
control.withdraw(); control.withdraw().map_err(failure)?;
} }
self.published_control.take();
Ok(()) Ok(())
} }
LifecycleAction::FinishTeardown => { LifecycleAction::FinishTeardown => {

View file

@ -1010,9 +1010,12 @@ impl PublishedControlFace {
} }
/// Withdraws exact discovery and connection paths while retaining live accepted streams. /// 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); /// # Errors
remove_if_identity_matches(&self.paths.identity, self.identity_file_identity); /// Refuses changed artifacts, removal failures, or artifacts that remain after removal.
pub fn withdraw(&self) -> Result<(), ControlError> {
remove_exact_if_identity_matches(&self.paths.socket, self.socket_identity)?;
remove_exact_if_identity_matches(&self.paths.identity, self.identity_file_identity)
} }
/// Accepts and serves one TLS connection. Registered connections remain live until EOF. /// Accepts and serves one TLS connection. Registered connections remain live until EOF.
@ -1260,7 +1263,32 @@ impl PublishedControlFace {
impl Drop for PublishedControlFace { impl Drop for PublishedControlFace {
fn drop(&mut self) { fn drop(&mut self) {
self.withdraw(); remove_if_identity_matches(&self.paths.socket, self.socket_identity);
remove_if_identity_matches(&self.paths.identity, self.identity_file_identity);
}
}
fn remove_exact_if_identity_matches(
path: &Path,
expected: FileIdentity,
) -> Result<(), ControlError> {
let metadata = match std::fs::symlink_metadata(path) {
Ok(metadata) => metadata,
Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(()),
Err(error) => return Err(error.into()),
};
if FileIdentity::from_metadata(&metadata) != expected {
return Err(ControlError::InvalidPublication(
"publication artifact identity changed before withdrawal",
));
}
std::fs::remove_file(path)?;
match std::fs::symlink_metadata(path) {
Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(()),
Err(error) => Err(error.into()),
Ok(_) => Err(ControlError::InvalidPublication(
"publication artifact remained after withdrawal",
)),
} }
} }

View file

@ -22,7 +22,7 @@ use lazaret_host_coordinator::control::{
}; };
use lazaret_host_coordinator::domain_store::{DomainStoreMutation, HostCoordinatorDomainStore}; use lazaret_host_coordinator::domain_store::{DomainStoreMutation, HostCoordinatorDomainStore};
use lazaret_host_coordinator::lifecycle::{ use lazaret_host_coordinator::lifecycle::{
HostCoordinatorLifecycle, LifecycleAction, LifecycleAdapter, DomainPhase, HostCoordinatorLifecycle, LifecycleAction, LifecycleAdapter,
}; };
use lazaret_linux::filesystem::try_exclusive_flock; use lazaret_linux::filesystem::try_exclusive_flock;
use rustix::process::getuid; use rustix::process::getuid;
@ -44,10 +44,12 @@ impl LifecycleAdapter for ControlLifecycleAdapter {
action: LifecycleAction, action: LifecycleAction,
_generation: &Generation, _generation: &Generation,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
if action == LifecycleAction::BeginTeardown if action == LifecycleAction::BeginTeardown {
&& let Some(face) = self.publication.lock().unwrap().take() let mut publication = self.publication.lock().unwrap();
{ if let Some(face) = publication.as_ref() {
face.withdraw(); face.withdraw()?;
}
publication.take();
} }
Ok(()) Ok(())
} }
@ -861,7 +863,16 @@ fn coordinated_control_session_tracks_lifecycle_and_withdraws_before_teardown()
Some(SessionPhase::Closed) Some(SessionPhase::Closed)
); );
assert!(lifecycle.is_teardown_ready()); assert!(lifecycle.is_teardown_ready());
std::fs::set_permissions(&directory.0, std::fs::Permissions::from_mode(0o500)).unwrap();
assert!(lifecycle.begin_teardown().is_err());
assert_eq!(lifecycle.phase(), DomainPhase::Ready);
assert!(lifecycle.is_published());
assert!(face.socket_path().exists());
assert!(face.identity_path().exists());
std::fs::set_permissions(&directory.0, std::fs::Permissions::from_mode(0o700)).unwrap();
lifecycle.begin_teardown().unwrap(); lifecycle.begin_teardown().unwrap();
assert_eq!(lifecycle.phase(), DomainPhase::Stopping);
assert!(!lifecycle.is_published());
assert!(!face.socket_path().exists()); assert!(!face.socket_path().exists());
assert!(!face.identity_path().exists()); assert!(!face.identity_path().exists());
assert!(std::os::unix::net::UnixStream::connect(face.socket_path()).is_err()); assert!(std::os::unix::net::UnixStream::connect(face.socket_path()).is_err());