fix(controller): un 429 sur la sonde ne marque plus la connexion en echec - #253
Conversation
There was a problem hiding this comment.
Le tri est correct. Probe renvoie bien un *APIError{Status: 429} (internal/ferrvault/client.go:123) et isRetriable ne réessaie que les 5xx et les erreurs de transport, donc le 429 remonte typé jusqu'à IsRateLimited au lieu d'être avalé par la boucle de retry. Les quatre return de probe sont cohérents avec la nouvelle signature, et rateLimitRequeue (20s) est bien la constante déjà déclarée dans le paquet (ferrvaultsecret_controller.go:38). Rien de bloquant.
Nit : le test ne couvre pas ce que le PR change. TestOnlyRateLimitingSkipsTheConditionUpdate appelle ferrvault.IsRateLimited directement, sans jamais passer par Reconcile ni par probe. Supprimer intégralement le nouveau bloc if ferrvault.IsRateLimited(probeErr) laisse le test vert. Ses cinq cas recouvrent par ailleurs TestIsRateLimited (internal/ferrvault/client_test.go:325), qui affirme déjà 429 → true, autres erreurs et nil → false.
Ce qui manque est le comportement affirmé par le titre du test : un httptest.Server répondant 429 sur /healthz, un fake client portant une connexion avec Ready=True et un LastCheckedAt connu, puis vérifier après Reconcile que la condition et l'horodatage sont inchangés et que Result.RequeueAfter == rateLimitRequeue. probe construit son client via ferrvault.New(conn.Spec.URL, token), donc pointer Spec.URL sur le serveur de test suffit : pas besoin d'injecter une factory comme côté secret. C'est le seul montage qui attraperait une régression sur la ligne ajoutée.
Nit : la sortie tôt n'appelle pas SetConnectionReady. Sur une connexion déjà réconciliée, la jauge garde sa valeur précédente, ce qui est cohérent avec « la condition n'est pas réécrite ». Mais la jauge vit dans le process : après un redémarrage de l'opérateur sous 429 persistant, ferrvault_connection_ready n'a aucune série pour les connexions concernées, et une alerte écrite sur == 0 reste muette. Contrairement au chemin secret, qui fait IncSyncError("RateLimited"), rien n'est compté ici, donc une limitation durable n'existe que dans les logs, alors que c'est précisément l'état où la condition est gelée. Un compteur sur ce chemin lèverait les deux points.
## [5.2.7] - 2026-09-06 ### Bug Fixes - fix(controller): un 429 sur la sonde ne marque plus la connexion en echec (#253)
Le pendant, pour les
FerrVaultConnection, de ce que #249 a fait pour lesFerrVaultSecret.Ce qui s'est passé en production
25 connexions sur 33 figées sur
Ready=False, motifUnreachable, message « ferrvault api error 429 ». Les instances FerrVault répondaient normalement, et tous lesFerrVaultSecretdépendant de ces connexions ont été entraînés avec elles.La cause est corrigée côté serveur — FerrLabs/FerrVault-Cloud#874 sort
/healthzdu limiteur, parce que la sonde n'étant pas authentifiée elle consommait un seau d'IP partagé par les 31 sondes du même pod. Vérifié en production : les 429 sont retombés à zéro.Mais le raisonnement restait faux ici : toute autre saturation reproduirait le symptôme.
Le correctif
Un 429 sort tôt, avec un report de 20 secondes, et la condition est laissée telle quelle : une connexion prête le reste, une connexion en échec garde la cause de son échec.
LastCheckedAtn'est pas touché non plus — l'horodater sur un contrôle qui n'a rien appris serait trompeur.proberend désormais l'erreur en plus de son message. C'était nécessaire : un premier jet tentaitIsRateLimited(errors.New(message)), ce qui ne peut rien reconnaître puisqu'une chaîne perd le type. La distinction ne se fait que sur l'erreur elle-même.Ce que le correctif ne fait pas
Il ne rend pas les vraies pannes silencieuses. Un 500, un 401 ou un Secret de jeton absent continuent d'écrire la condition : une connexion cassée qui resterait verte serait l'inverse exact du défaut corrigé, et bien pire. Le test couvre les cinq cas.
Vérification, et sa limite
Go n'est pas installé sur la machine où ceci est écrit, donc ni
go buildnigo testn'ont tourné localement. Je m'en remets à la CI et je le signale plutôt que de le passer sous silence.Vérifié à la main :
returndeprobeportent bien quatre valeurs, cohérentes avec la nouvelle signature ;proben'a qu'un seul appelant, mis à jour ;rateLimitRequeuen'est pas redéclaré : un premier jet en ajoutait une copie alors que la constante existe déjà dans le même paquet, ce qui n'aurait pas compilé ;timeetferrvaultsont déjà importés ;errors.Assurnilrenvoiefalsesans paniquer, donc le chemin nominal est sûr ;APIError{Status, Message}etAuthError{Kind, Message}du test correspondent aux définitions.