Gestion du cache des jukebox #17
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Les modifications récente de la gestion des jukebox à introduit un gros bug (et probablement un autre) :
Ce cache doit être mis à jour dès que la status des jukebox est modifiés, indépendamment de la fin du job de scan.
Analyse
Les deux bugs sont confirmés dans le code. Ils ont été introduits par le commit
d39ed30(« fix: réconciliation JukeBox ne bloque plus sur le job de scan »), qui a remplacé l'attente bloquante de fin de job par une garde en tête de réconciliation (operator/src/jukebox.rs).Cause racine commune
La mise à jour du cache des packages est conditionnée à l'état du Job
scan-<name>(terminal + annotationvynil.solidite.fr/last-scan-time), alors que la donnée du cache provient du status du JukeBox, que l'agent patche directement en fin de scan (set_status_updated/set_status_packages_merge). Le signal utilisé n'est pas le bon : c'est le changement de status qui devrait déclencher la mise à jour, et ce signal existe déjà — le controller kube-rs déclenche une réconciliation sur tout changement de l'objet JukeBox, status inclus.Bug 1 — latence d'1 minute après la fin du scan
Séquence observée :
Complete→ la garde retournerequeue(60s)avant toute mise à jour du cache (jukebox.rs:60-66).Completene génère aucun événement (le controller ne watch pas les Jobs, pas de.owns()), donc le cache attend le requeue de 60 s.Bug 2 — les scans cron ne mettent plus à jour le cache
Le CronJob
scan-<name>crée des Jobs à noms générés (scan-<name>-<timestamp>), mais la garde n'inspecte que le Job directscan-<name>(jukebox.rs:52-56). Séquence :scan-<name>, terminal depuis longtemps, dont lacompletionTimecorrespond déjà à l'annotationlast-scan-time→already_processed = true→ cache jamais mis à jour.Le cache ne se rafraîchit alors qu'au redémarrage de l'opérateur ou lors d'un nouveau scan direct (force-scan / création).
Correction proposée
Décorréler complètement la mise à jour du cache de l'état des Jobs, comme demandé :
last-scan-time(annotation + lecture decompletionTime).reconcile, avant la garde sur le job en cours : comparerself.status.packages(+spec.pull_secret) avec l'entrée du cache pour ce jukebox, et faire un upsert par jukebox si différent. L'objet reçu par le watch est déjà frais : pas besoin de re-lister tous les JukeBox (évite aussi la course liste/patch), pas de patch d'annotation.cleanup()lors de la suppression d'un JukeBox (aujourd'huiset_package_cachereconstruit tout, un upsert par jukebox nécessite une suppression explicite).Couverture systématique : scan direct, scan cron, force-scan et édition manuelle du status, avec une latence quasi nulle (réconciliation déclenchée par le patch de status lui-même).
Si l'analyse te convient, je crée l'issue GitHub miroir et la PR associée.
Analyse rédigée par Claude (assistant IA), pour le compte de @reivaxm.
Complément — lien de causalité avec #9 et plan de test complet
Lien avec #9 confirmé
Le mécanisme introduit par
d39ed30(garde + fall-through) combiné à0633523(force-scan partiel) est aussi la cause du symptôme de #9. Cycle complet :force-scan: <cat>/<pkg>posée → l'opérateur supprime le job direct et le recrée avecSCAN_PACKAGE. Le job partiel tourne, protégé par la garde.scan.yamlsans filtre (annotation consommée).spec.templateétant immuable, l'apply échoue → la branche de secours (jukebox.rs:167-187) supprime le job partiel et lance un scan complet.Donc : chaque scan partiel est suivi d'un scan complet non demandé (~0-60 s après), le job partiel étant remplacé par un job de même nom sans
SCAN_PACKAGE— d'où l'impression que l'agent ne reçoit jamais le filtre. Détail sur #9.Correction consolidée (les deux issues, sans casser la feature force-scan)
self.statusen tête de réconciliation, indépendant des Jobs ; suppression du mécanismelast-scan-time; retrait de l'entrée danscleanup().force-scanest présent. Un job terminal sans annotation n'est jamais touché ; les rescans périodiques restent au CronJob.log_warn+ merge vide à la place).Plan de test (TDD : écrits avant la correction, échouent sur le code actuel)
Opérateur —
reconcile()/cleanup()avec client kube mocké (tower_test::mock, comme dansagent/src/boxes/scan.rs) :Remédiation #17 :
cache_updated_from_status_while_job_running— status à jour, job non terminal → cache mis à jour malgré le requeue (échoue aujourd'hui : early-return avant le cache).cache_updated_on_cron_scan_status_change— job direct terminal aveccompletionTime== annotation (déjà traité), status modifié par un job cron → cache mis à jour (échoue aujourd'hui :already_processed).cache_upsert_preserves_other_jukeboxes— l'upsert du jukebox A ne supprime pas l'entrée du jukebox B.cache_idempotent_when_status_unchanged— pas d'écriture si le status n'a pas changé.cleanup_removes_cache_entry— suppression du JukeBox → entrée retirée du cache.Remédiation #9 (côté opérateur) :
terminal_partial_job_not_replaced_by_full_scan— job terminal portantSCAN_PACKAGE, pas d'annotation → aucun delete/patch/create du Job (échoue aujourd'hui : delete + recréation full scan).running_job_never_touched— job en cours, pas d'annotation → aucun appel d'écriture sur le Job.Non-régression des features existantes :
initial_scan_job_created_when_absent— pas de Job existant → création (scan initial, comportement d'origine).force_scan_partial_creates_job_with_scan_package— annotationapps/monappli→ delete + create avecSCAN_PACKAGE=apps/monappli, annotation retirée après succès (0633523).force_scan_true_creates_full_scan_job— valeurtrue→ job sansSCAN_PACKAGE(0633523).force_scan_annotation_kept_on_job_creation_failure— échec de création → annotation conservée pour retry (d78cba0).reconcile_returns_quickly_on_running_job— job en cours → requeue rapide, pas d'attente bloquante (d39ed30).cronjob_always_applied— le CronJob est maintenu dans tous les chemins.Agent —
scan.rhai(harnais de test rhai existant) :partial_filter_limits_to_matched_repos— filtre présent dans le status → seuls les couples registry/image correspondants sont interrogés.partial_filter_no_match_does_not_full_scan— filtre sans correspondance → aucun appel registre, warning, status inchangé (échoue aujourd'hui : fallbackelse { list }).set_status_packages_merge_preserves_other_packages— le merge partiel ne perd pas les packages hors filtre.Si ça te convient, je crée l'issue GitHub miroir (référençant #9 et #17) et la PR avec tests-d'abord.
Analyse rédigée par Claude (assistant IA), pour le compte de @reivaxm.