Fix systemd unloaded unit

This commit is contained in:
2026-10-27 22:21:44 +02:00
parent 86c28a35b5
commit fe7e3d61c2
7 changed files with 763 additions and 43 deletions
+142 -1
View File
@@ -315,6 +315,9 @@ pub struct RestartDiagnostic {
pub struct RestartReport {
pub stage: RestartStage,
pub failure: Option<RestartFailureKind>,
pub service_failure: Option<service::RestartFailure>,
pub service_phase: Option<service::RestartPhase>,
pub systemd_unit: Option<String>,
pub backup_path: Option<std::path::PathBuf>,
pub restart_submitted: bool,
pub restart_may_still_complete: bool,
@@ -1713,6 +1716,7 @@ impl Application {
let execution = service(&settings.systemd_unit, deadline);
let job = execution.outcome;
let submitted = execution.submitted;
let service_phase = execution.phase;
if job != RestartOutcome::Succeeded {
self.active_restart_attempt = None;
let may_complete = submitted
@@ -1727,7 +1731,7 @@ impl Application {
| service::RestartFailure::JobResultUnknown
)
);
return restart_report(
let mut report = restart_report(
RestartStage::Service,
Some(RestartFailureKind::ServiceFailed),
Some(backup_path),
@@ -1735,6 +1739,13 @@ impl Application {
may_complete,
submitted,
);
report.service_failure = match job {
RestartOutcome::Failed(failure) => Some(failure),
RestartOutcome::Waiting | RestartOutcome::Succeeded => None,
};
report.service_phase = Some(service_phase);
report.systemd_unit = Some(settings.systemd_unit);
return report;
}
let remaining = deadline.remaining_at(now());
if remaining.is_zero() {
@@ -2411,6 +2422,9 @@ fn restart_report(
RestartReport {
stage,
failure,
service_failure: None,
service_phase: None,
systemd_unit: None,
backup_path,
restart_submitted,
restart_may_still_complete,
@@ -4172,6 +4186,7 @@ restart_timeout_seconds = 30
RestartExecution {
outcome: RestartOutcome::Succeeded,
submitted: true,
phase: crate::service::RestartPhase::WaitForJob,
}
},
|_, _| {
@@ -4210,6 +4225,7 @@ restart_timeout_seconds = 30
RestartExecution {
outcome: RestartOutcome::Succeeded,
submitted: true,
phase: crate::service::RestartPhase::WaitForJob,
}
},
|_, _| Err(TransportError::Transport),
@@ -4250,6 +4266,7 @@ restart_timeout_seconds = 30
RestartExecution {
outcome: RestartOutcome::Succeeded,
submitted: true,
phase: crate::service::RestartPhase::WaitForJob,
}
},
|interface, remaining| {
@@ -4327,6 +4344,7 @@ restart_timeout_seconds = 30
RestartExecution {
outcome: RestartOutcome::Succeeded,
submitted: true,
phase: crate::service::RestartPhase::WaitForJob,
}
},
|_, _| Ok(observation),
@@ -4445,6 +4463,7 @@ restart_timeout_seconds = 30
|_, _| RestartExecution {
outcome: RestartOutcome::Succeeded,
submitted: true,
phase: crate::service::RestartPhase::WaitForJob,
},
|_, _| Ok(incomplete),
|store, saved, backup| store.install(saved, backup),
@@ -4513,6 +4532,7 @@ restart_timeout_seconds = 30
|_, _| RestartExecution {
outcome: RestartOutcome::Failed(RestartFailure::WaitTimedOutMayStillComplete),
submitted: true,
phase: crate::service::RestartPhase::WaitForJob,
},
|_, _| {
runtime_called.set(true);
@@ -4548,6 +4568,7 @@ restart_timeout_seconds = 30
RestartExecution {
outcome: RestartOutcome::Succeeded,
submitted: true,
phase: crate::service::RestartPhase::WaitForJob,
}
},
|_, _| {
@@ -4595,6 +4616,7 @@ restart_timeout_seconds = 30
RestartExecution {
outcome: RestartOutcome::Succeeded,
submitted: true,
phase: crate::service::RestartPhase::WaitForJob,
}
},
|_, remaining| {
@@ -4660,6 +4682,7 @@ restart_timeout_seconds = 30
|_, _| RestartExecution {
outcome: RestartOutcome::Succeeded,
submitted: true,
phase: crate::service::RestartPhase::WaitForJob,
},
|_, _| Err(error),
|store, saved, backup| {
@@ -4698,6 +4721,7 @@ restart_timeout_seconds = 30
|_, _| RestartExecution {
outcome: RestartOutcome::Failed(failure),
submitted: true,
phase: crate::service::RestartPhase::WaitForJob,
},
|_, _| unreachable!("terminal service failure must stop before runtime read"),
|_, _, _| unreachable!("terminal service failure must stop before persistence"),
@@ -4710,6 +4734,120 @@ restart_timeout_seconds = 30
}
}
#[test]
fn application_report_preserves_every_service_failure_and_operation_phase() {
use crate::service::RestartPhase;
let cases = [
(RestartFailure::JobFailed, RestartPhase::WaitForJob, true),
(
RestartFailure::JobDependency,
RestartPhase::WaitForJob,
true,
),
(RestartFailure::JobTimedOut, RestartPhase::WaitForJob, true),
(RestartFailure::JobCanceled, RestartPhase::WaitForJob, true),
(RestartFailure::JobSkipped, RestartPhase::WaitForJob, true),
(
RestartFailure::JobResultUnknown,
RestartPhase::WaitForJob,
true,
),
(
RestartFailure::BusDisconnected,
RestartPhase::Connect,
false,
),
(
RestartFailure::ManagerOwnerChanged,
RestartPhase::WaitForJob,
true,
),
(
RestartFailure::ManagerOwnerChanged,
RestartPhase::RequestRestart,
true,
),
(
RestartFailure::SubscriptionFailed,
RestartPhase::Subscribe,
false,
),
(
RestartFailure::UnitResolutionFailed,
RestartPhase::ResolveUnit,
false,
),
(
RestartFailure::PermissionDenied,
RestartPhase::ResolveUnit,
false,
),
(
RestartFailure::RequestFailed,
RestartPhase::RequestRestart,
false,
),
(
RestartFailure::WaitTimedOutMayStillComplete,
RestartPhase::WaitForJob,
true,
),
(
RestartFailure::EarlyEventBufferFull,
RestartPhase::WaitForJob,
true,
),
(
RestartFailure::InvalidJobPath,
RestartPhase::WaitForJob,
true,
),
];
let root = tempfile::tempdir().expect("temp directory");
let (mut app, _) = prepare_restartable(root.path());
for (failure, phase, submitted) in cases {
let preview = app.preview_restart().expect("preview saved generation");
let report = app.restart_with_adapters(
preview,
RestartChoice::RestartSavedState,
|_, _| RestartExecution {
outcome: RestartOutcome::Failed(failure),
submitted,
phase,
},
|_, _| unreachable!("service failure must stop before runtime read"),
|_, _, _| unreachable!("service failure must stop before persistence"),
);
assert_eq!(report.failure, Some(RestartFailureKind::ServiceFailed));
assert_eq!(report.service_failure, Some(failure));
assert_eq!(report.service_phase, Some(phase));
assert_eq!(report.systemd_unit.as_deref(), Some("wg-quick@wg0.service"));
assert_eq!(report.restart_submitted, submitted);
if failure == RestartFailure::ManagerOwnerChanged
&& phase == RestartPhase::RequestRestart
{
assert!(report.restart_may_still_complete);
assert!(report.manual_recovery_required);
}
assert_eq!(
report.restart_may_still_complete,
submitted
&& !matches!(
failure,
RestartFailure::JobFailed
| RestartFailure::JobDependency
| RestartFailure::JobTimedOut
| RestartFailure::JobCanceled
| RestartFailure::JobSkipped
| RestartFailure::JobResultUnknown
)
);
assert_eq!(app.states().applied(), &RuntimeObservation::Unknown);
}
}
#[test]
fn restart_mismatch_and_post_restart_write_failure_never_release_addresses() {
let root = tempfile::tempdir().expect("temp directory");
@@ -4724,6 +4862,7 @@ restart_timeout_seconds = 30
|_, _| RestartExecution {
outcome: RestartOutcome::Succeeded,
submitted: true,
phase: crate::service::RestartPhase::WaitForJob,
},
|_, _| {
Ok(ObservedRuntime {
@@ -4744,6 +4883,7 @@ restart_timeout_seconds = 30
|_, _| RestartExecution {
outcome: RestartOutcome::Succeeded,
submitted: true,
phase: crate::service::RestartPhase::WaitForJob,
},
|_, _| Ok(observation),
|_, _, _| Err(PersistenceError::RecoveryRequired),
@@ -4803,6 +4943,7 @@ restart_timeout_seconds = 30
RestartExecution {
outcome: RestartOutcome::Succeeded,
submitted: true,
phase: crate::service::RestartPhase::WaitForJob,
}
},
|_, _| Err(TransportError::Transport),