fix: add package cache refresh before apply and on health check
- New src/packages/cache.rs module with PackageCacheState, stale detection, state persistence, 404 retry logic - Add refresh_package_cache() and last_cache_update() to PackageManagerBackend trait, implemented on all 5 backends (APT, DNF, YUM, APK, Pacman) - Health check now reports last_cache_update and cache_status fields, triggers cache refresh if stale (>4h), returns degraded on failure - Patch apply jobs now force cache refresh before applying patches, with 404/fetch error retry (1 retry after cache refresh) - Cache state persists to /var/lib/linux_patch_api/state/cache.json - Version bump to 1.1.17 - Update ARCHITECTURE.md and REQUIREMENTS.md (FR-007) Closes: #2
This commit is contained in:
@ -81,6 +81,7 @@ pub async fn apply_patches(
|
||||
body: web::Json<PatchApplyRequest>,
|
||||
backend: web::Data<Box<dyn PackageManagerBackend>>,
|
||||
job_manager: web::Data<JobManager>,
|
||||
cache_state: web::Data<crate::packages::cache::PackageCacheState>,
|
||||
_req: HttpRequest,
|
||||
) -> impl Responder {
|
||||
let request_id = Uuid::new_v4().to_string();
|
||||
@ -104,6 +105,7 @@ pub async fn apply_patches(
|
||||
// Spawn background task to execute the patching
|
||||
let backend_clone = backend.clone();
|
||||
let job_manager_clone = job_manager.clone();
|
||||
let cache_state_clone = cache_state.clone();
|
||||
let request = body.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
@ -122,8 +124,39 @@ pub async fn apply_patches(
|
||||
.add_job_log(&job_id_clone, "Job started".to_string())
|
||||
.await;
|
||||
|
||||
// Execute patching
|
||||
match backend_clone.apply_patches(request.packages.as_deref()) {
|
||||
// MANDATORY: Refresh package cache before applying patches
|
||||
let _ = job_manager_clone
|
||||
.update_job(&job_id_clone, JobStatus::Running, Some(0), Some("Refreshing package index...".to_string()))
|
||||
.await;
|
||||
let _ = job_manager_clone
|
||||
.add_job_log(&job_id_clone, "Refreshing package cache...".to_string())
|
||||
.await;
|
||||
|
||||
match backend_clone.refresh_package_cache(&cache_state_clone) {
|
||||
Ok(_) => {
|
||||
let _ = job_manager_clone
|
||||
.add_job_log(&job_id_clone, "Package cache refreshed successfully".to_string())
|
||||
.await;
|
||||
let _ = job_manager_clone
|
||||
.update_job(&job_id_clone, JobStatus::Running, Some(10), Some("Cache refreshed, applying patches...".to_string()))
|
||||
.await;
|
||||
}
|
||||
Err(e) => {
|
||||
let err_msg = format!("Package cache refresh failed: {}", e);
|
||||
error!(job_id = %job_id_clone, error = %e, "Cache refresh failed");
|
||||
let _ = job_manager_clone
|
||||
.add_job_log(&job_id_clone, err_msg.clone())
|
||||
.await;
|
||||
let _ = job_manager_clone.fail_job(&job_id_clone, err_msg).await;
|
||||
return; // Exit the spawned task
|
||||
}
|
||||
}
|
||||
|
||||
// Execute patching with 404 retry
|
||||
let packages_ref = request.packages.as_deref();
|
||||
let apply_result = backend_clone.apply_patches(packages_ref);
|
||||
|
||||
match apply_result {
|
||||
Ok(_) => {
|
||||
let _ = job_manager_clone.complete_job(&job_id_clone).await;
|
||||
info!(job_id = %job_id_clone, "Patch application completed");
|
||||
@ -157,10 +190,67 @@ pub async fn apply_patches(
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
Err(e) if crate::packages::cache::is_fetch_error(&e) => {
|
||||
// 404/fetch error: refresh cache and retry once
|
||||
info!(job_id = %job_id_clone, "Patch apply failed with fetch error, refreshing cache and retrying");
|
||||
let _ = job_manager_clone
|
||||
.fail_job(&job_id_clone, e.to_string())
|
||||
.add_job_log(&job_id_clone, "Fetch error detected, refreshing cache and retrying...".to_string())
|
||||
.await;
|
||||
|
||||
match backend_clone.refresh_package_cache(&cache_state_clone) {
|
||||
Ok(_) => {
|
||||
let _ = job_manager_clone
|
||||
.add_job_log(&job_id_clone, "Cache refreshed, retrying patch apply...".to_string())
|
||||
.await;
|
||||
}
|
||||
Err(refresh_err) => {
|
||||
let err_msg = format!("Cache refresh on retry failed: {}", refresh_err);
|
||||
let _ = job_manager_clone.fail_job(&job_id_clone, err_msg).await;
|
||||
error!(job_id = %job_id_clone, error = %refresh_err, "Cache refresh on retry failed");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Retry the apply
|
||||
match backend_clone.apply_patches(packages_ref) {
|
||||
Ok(_) => {
|
||||
let _ = job_manager_clone.complete_job(&job_id_clone).await;
|
||||
info!(job_id = %job_id_clone, "Patch application completed after retry");
|
||||
|
||||
// Handle reboot if requested
|
||||
if request.reboot {
|
||||
let _ = job_manager_clone
|
||||
.add_job_log(
|
||||
&job_id_clone,
|
||||
format!(
|
||||
"Reboot scheduled in {} seconds",
|
||||
request.reboot_delay_seconds
|
||||
),
|
||||
)
|
||||
.await;
|
||||
match backend_clone.reboot_system(request.reboot_delay_seconds) {
|
||||
Ok(_) => {
|
||||
let _ = job_manager_clone
|
||||
.add_job_log(&job_id_clone, "Reboot command executed".to_string())
|
||||
.await;
|
||||
}
|
||||
Err(e) => {
|
||||
let _ = job_manager_clone
|
||||
.add_job_log(&job_id_clone, format!("Reboot failed: {}", e))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(retry_err) => {
|
||||
let _ = job_manager_clone.fail_job(&job_id_clone, retry_err.to_string()).await;
|
||||
error!(job_id = %job_id_clone, error = %retry_err, "Patch application failed after retry");
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
// Non-fetch error: fail immediately
|
||||
let _ = job_manager_clone.fail_job(&job_id_clone, e.to_string()).await;
|
||||
error!(job_id = %job_id_clone, error = %e, "Patch application failed");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user