Gate terminal group cleanup on live authority
This commit is contained in:
parent
4dc5daa57b
commit
c5ed009fb8
1 changed files with 355 additions and 48 deletions
|
|
@ -235,6 +235,7 @@ struct MockGuestState {
|
|||
group_present: bool,
|
||||
leader_present: bool,
|
||||
identity_mismatch: bool,
|
||||
clears_after_signal: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -258,6 +259,7 @@ enum StoreFailurePoint {
|
|||
struct FailingRunningStore {
|
||||
inner: Arc<HostCoordinatorDomainStore>,
|
||||
failure: Mutex<Option<(StoreFailurePoint, bool)>>,
|
||||
snapshot_failure: Mutex<bool>,
|
||||
}
|
||||
|
||||
impl FailingRunningStore {
|
||||
|
|
@ -265,16 +267,29 @@ impl FailingRunningStore {
|
|||
Self {
|
||||
inner,
|
||||
failure: Mutex::new(None),
|
||||
snapshot_failure: Mutex::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
fn arm(&self, point: StoreFailurePoint, persistent: bool) {
|
||||
*self.failure.lock().unwrap() = Some((point, persistent));
|
||||
}
|
||||
|
||||
fn arm_snapshot_failure(&self) {
|
||||
*self.snapshot_failure.lock().unwrap() = true;
|
||||
}
|
||||
}
|
||||
|
||||
impl TerminalDomainStore for FailingRunningStore {
|
||||
fn snapshot(&self) -> Result<lazaret_core::records::DomainStore, DomainStoreError> {
|
||||
let mut failure = self.snapshot_failure.lock().unwrap();
|
||||
if *failure {
|
||||
*failure = false;
|
||||
return Err(DomainStoreError::Io(io::Error::other(
|
||||
"injected authority read failure",
|
||||
)));
|
||||
}
|
||||
drop(failure);
|
||||
self.inner.snapshot()
|
||||
}
|
||||
|
||||
|
|
@ -345,7 +360,7 @@ impl SessionTerminalProcess for MockProcess {
|
|||
return Err(TerminalError::GuestIdentityMismatch);
|
||||
}
|
||||
state.log.lock().unwrap().push(format!("signal:{signal}"));
|
||||
if signal == "HUP" {
|
||||
if signal == state.guest.clears_after_signal {
|
||||
state.guest.group_present = false;
|
||||
}
|
||||
Ok(())
|
||||
|
|
@ -446,6 +461,7 @@ impl SessionTerminalFactory for MockFactory {
|
|||
group_present: true,
|
||||
leader_present: true,
|
||||
identity_mismatch: false,
|
||||
clears_after_signal: "HUP",
|
||||
},
|
||||
resources: MockResourceState {
|
||||
exec_exited: false,
|
||||
|
|
@ -489,12 +505,20 @@ fn setup(ids: &[SessionId]) -> (Fixture, Arc<MockFactory>, SessionTerminalEngine
|
|||
}
|
||||
|
||||
fn engine_for(fixture: &Fixture) -> (Arc<MockFactory>, SessionTerminalEngine) {
|
||||
let store: Arc<dyn TerminalDomainStore> = fixture.store.clone();
|
||||
engine_for_store(store)
|
||||
}
|
||||
|
||||
fn engine_for_store(
|
||||
store: Arc<dyn TerminalDomainStore>,
|
||||
) -> (Arc<MockFactory>, SessionTerminalEngine) {
|
||||
let factory = Arc::new(MockFactory::default());
|
||||
let engine = SessionTerminalEngine::new(
|
||||
Arc::clone(&fixture.store),
|
||||
factory.clone(),
|
||||
Arc::new(Cancellation(Arc::clone(&factory.log))),
|
||||
);
|
||||
let engine = SessionTerminalEngine {
|
||||
recorder: HostCoordinatorTerminalRecorder::with_store(store),
|
||||
factory: factory.clone(),
|
||||
cancellation: Arc::new(Cancellation(Arc::clone(&factory.log))),
|
||||
active: HashMap::new(),
|
||||
};
|
||||
(factory, engine)
|
||||
}
|
||||
|
||||
|
|
@ -502,6 +526,17 @@ fn id(value: u8) -> SessionId {
|
|||
SessionId::new(bytes(value))
|
||||
}
|
||||
|
||||
fn assert_no_group_signal(factory: &MockFactory) {
|
||||
assert!(
|
||||
factory
|
||||
.log
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.all(|action| !action.starts_with("signal:"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn acquisition_refuses_authority_outside_the_committed_sandbox() {
|
||||
let session = id(6);
|
||||
|
|
@ -552,7 +587,7 @@ fn acquisition_requires_attached_current_session() {
|
|||
fn durable_detach_failure_and_sandbox_release_revoke_active_terminal() {
|
||||
for revoke in ["closed", "failed", "sandbox-release"] {
|
||||
let session = id(6);
|
||||
let (fixture, _factory, mut engine) = setup(&[session]);
|
||||
let (fixture, factory, mut engine) = setup(&[session]);
|
||||
engine.acquire(&acquisition(session)).unwrap();
|
||||
match revoke {
|
||||
"closed" => fixture.set_session_phase(session, SessionPhase::Closed),
|
||||
|
|
@ -571,13 +606,94 @@ fn durable_detach_failure_and_sandbox_release_revoke_active_terminal() {
|
|||
Err(TerminalError::AuthorityMismatch)
|
||||
));
|
||||
assert!(!engine.contains(session));
|
||||
assert_no_group_signal(&factory);
|
||||
assert_eq!(
|
||||
fixture.store.snapshot().unwrap().session_terminals[0].state(),
|
||||
SessionTerminalState::Clean
|
||||
SessionTerminalState::ReleaseIntent
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn domain_container_hc_and_authority_read_mismatch_use_host_only_release() {
|
||||
for mismatch in ["domain", "container", "host-coordinator"] {
|
||||
let session = id(6);
|
||||
let (fixture, factory, mut engine) = setup(&[session]);
|
||||
engine.acquire(&acquisition(session)).unwrap();
|
||||
let request = &mut engine.active.get_mut(&session).unwrap().request;
|
||||
match mismatch {
|
||||
"domain" => request.domain_id = DomainId::new(bytes(80)),
|
||||
"container" => request.container_id = ContainerId::new(bytes(81)),
|
||||
"host-coordinator" => request.host_coordinator = process(800),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
assert!(matches!(
|
||||
engine.handle(
|
||||
GenerationId::new(7),
|
||||
SessionTerminalEvent::Input(TerminalInput {
|
||||
session_id: session,
|
||||
bytes: b"refused".to_vec(),
|
||||
}),
|
||||
),
|
||||
Err(TerminalError::AuthorityMismatch)
|
||||
));
|
||||
assert_no_group_signal(&factory);
|
||||
assert_eq!(
|
||||
fixture.store.snapshot().unwrap().session_terminals[0].state(),
|
||||
SessionTerminalState::ReleaseIntent
|
||||
);
|
||||
}
|
||||
|
||||
let session = id(6);
|
||||
let fixture = Fixture::new(&[session]);
|
||||
let store = Arc::new(FailingRunningStore::new(Arc::clone(&fixture.store)));
|
||||
let (factory, mut engine) = engine_for_store(store.clone());
|
||||
engine.acquire(&acquisition(session)).unwrap();
|
||||
store.arm_snapshot_failure();
|
||||
assert!(matches!(
|
||||
engine.poll(session, GenerationId::new(7)),
|
||||
Err(TerminalError::Store(DomainStoreError::Io(_)))
|
||||
));
|
||||
assert_no_group_signal(&factory);
|
||||
assert_eq!(
|
||||
fixture.store.snapshot().unwrap().session_terminals[0].state(),
|
||||
SessionTerminalState::ReleaseIntent
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_close_and_loss_revalidate_before_guest_cleanup() {
|
||||
let session = id(6);
|
||||
let (fixture, factory, mut engine) = setup(&[session]);
|
||||
engine.acquire(&acquisition(session)).unwrap();
|
||||
fixture.release_sandbox();
|
||||
engine
|
||||
.handle(
|
||||
GenerationId::new(7),
|
||||
SessionTerminalEvent::Close(lazaret_core::records::SessionClose {
|
||||
session_id: session,
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
assert_no_group_signal(&factory);
|
||||
assert_eq!(
|
||||
fixture.store.snapshot().unwrap().session_terminals[0].state(),
|
||||
SessionTerminalState::ReleaseIntent
|
||||
);
|
||||
|
||||
let (fixture, factory, mut engine) = setup(&[session]);
|
||||
engine.acquire(&acquisition(session)).unwrap();
|
||||
fixture.add_newer_generation();
|
||||
engine
|
||||
.lose(session, GenerationId::new(7), TerminalLoss::PtyLost)
|
||||
.unwrap();
|
||||
assert_no_group_signal(&factory);
|
||||
assert_eq!(
|
||||
fixture.store.snapshot().unwrap().session_terminals[0].state(),
|
||||
SessionTerminalState::ReleaseIntent
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stale_caller_and_superseded_generation_refuse_without_cross_generation_action() {
|
||||
let session = id(6);
|
||||
|
|
@ -593,15 +709,26 @@ fn stale_caller_and_superseded_generation_refuse_without_cross_generation_action
|
|||
),
|
||||
Err(TerminalError::StaleGeneration)
|
||||
));
|
||||
assert!(factory.log.lock().unwrap().is_empty());
|
||||
assert!(engine.contains(session));
|
||||
assert_no_group_signal(&factory);
|
||||
assert!(!engine.contains(session));
|
||||
assert_eq!(
|
||||
fixture.store.snapshot().unwrap().session_terminals[0].state(),
|
||||
SessionTerminalState::ReleaseIntent
|
||||
);
|
||||
|
||||
let (fixture, factory, mut engine) = setup(&[session]);
|
||||
engine.acquire(&acquisition(session)).unwrap();
|
||||
fixture.add_newer_generation();
|
||||
assert!(matches!(
|
||||
engine.poll(session, GenerationId::new(7)),
|
||||
Err(TerminalError::AuthorityMismatch)
|
||||
));
|
||||
assert!(!engine.contains(session));
|
||||
assert_no_group_signal(&factory);
|
||||
assert_eq!(
|
||||
fixture.store.snapshot().unwrap().session_terminals[0].state(),
|
||||
SessionTerminalState::ReleaseIntent
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -789,6 +916,7 @@ fn running_store_failures_release_every_live_partial_handle() {
|
|||
group_present: true,
|
||||
leader_present: true,
|
||||
identity_mismatch: false,
|
||||
clears_after_signal: "HUP",
|
||||
},
|
||||
resources: MockResourceState {
|
||||
exec_exited: false,
|
||||
|
|
@ -818,7 +946,16 @@ fn running_store_failures_release_every_live_partial_handle() {
|
|||
error => panic!("unexpected cleanup result: {error}"),
|
||||
}
|
||||
let state = state.lock().unwrap();
|
||||
assert!(!state.guest.group_present);
|
||||
assert_eq!(state.guest.group_present, persistent);
|
||||
assert_eq!(
|
||||
state
|
||||
.log
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.any(|action| action.starts_with("signal:")),
|
||||
!persistent
|
||||
);
|
||||
assert!(state.resources.exec_exited);
|
||||
assert!(state.resources.exec_reaped);
|
||||
assert!(!state.resources.pty_open);
|
||||
|
|
@ -886,6 +1023,7 @@ fn close_cancels_route_then_bounds_drain_and_emits_one_final_exit() {
|
|||
let session = id(6);
|
||||
let (fixture, factory, mut engine) = setup(&[session]);
|
||||
engine.acquire(&acquisition(session)).unwrap();
|
||||
factory.state(0).lock().unwrap().guest.clears_after_signal = "KILL";
|
||||
factory
|
||||
.state(0)
|
||||
.lock()
|
||||
|
|
@ -911,10 +1049,16 @@ fn close_cancels_route_then_bounds_drain_and_emits_one_final_exit() {
|
|||
assert!(
|
||||
matches!(messages.last(), Some(SessionTerminalMessage::Exit(exit)) if exit.exit_code() == Some(17))
|
||||
);
|
||||
let log = factory.log.lock().unwrap();
|
||||
assert_eq!(log[0], "cancel-route");
|
||||
assert_eq!(
|
||||
&factory.log.lock().unwrap()[..2],
|
||||
&["cancel-route", "signal:HUP"]
|
||||
log.iter()
|
||||
.filter(|action| action.starts_with("signal:"))
|
||||
.map(String::as_str)
|
||||
.collect::<Vec<_>>(),
|
||||
["signal:HUP", "signal:TERM", "signal:KILL"]
|
||||
);
|
||||
drop(log);
|
||||
assert!(!engine.contains(session));
|
||||
assert!(matches!(
|
||||
engine.handle(
|
||||
|
|
@ -951,7 +1095,7 @@ fn disconnect_component_loss_and_hard_hc_loss_never_reattach() {
|
|||
}
|
||||
|
||||
let session = id(6);
|
||||
let (fixture, _factory, mut engine) = setup(&[session]);
|
||||
let (fixture, factory, mut engine) = setup(&[session]);
|
||||
engine.acquire(&acquisition(session)).unwrap();
|
||||
assert!(
|
||||
engine
|
||||
|
|
@ -964,6 +1108,7 @@ fn disconnect_component_loss_and_hard_hc_loss_never_reattach() {
|
|||
.is_empty()
|
||||
);
|
||||
assert!(!engine.contains(session));
|
||||
assert_no_group_signal(&factory);
|
||||
assert_eq!(
|
||||
fixture.store.snapshot().unwrap().session_terminals[0].state(),
|
||||
SessionTerminalState::Running
|
||||
|
|
|
|||
Loading…
Reference in a new issue