diff --git a/long_test/qemu_cache.py b/long_test/qemu_cache.py index e2d5259..b259f17 100755 --- a/long_test/qemu_cache.py +++ b/long_test/qemu_cache.py @@ -828,7 +828,24 @@ def verdict(premier, second, journal): vues1 = {l["url"] for l in p1} # Le critère : ce que les DEUX ont demandé ne doit pas être ressorti. - fautes = [l for l in p2 if l["url"] in vues1 and l.get("upstream")] + # + # Un REFUS de l'amont n'en est pas : un miroir qui range sa distribution + # sous un autre chemin répond 404 à chaque paquet, le client passe au + # miroir suivant, et le cache sert celui-là du disque. Il n'a rien livré + # que le cache aurait dû garder — un 404 figé masquerait le fichier publié + # ensuite. Le compter en faute fait échouer une mesure où tout a été servi. + refus = [ + l + for l in p2 + if l.get("upstream") + and isinstance(l.get("status"), int) + and l["status"] >= 400 + ] + fautes = [ + l + for l in p2 + if l["url"] in vues1 and l.get("upstream") and l not in refus + ] # Ce que la seconde a découvert seule : légitime sur une publication # continue, montré pour que personne ne prenne un miroir qui bouge pour # une panne de cache. @@ -901,6 +918,12 @@ def verdict(premier, second, journal): ) for l in neufs[:5]: dire(f" + {l['url'].rsplit('/', 1)[-1]}", journal) + if refus: + dire( + f" ({len(refus)} refus de l'amont, non comptés : un miroir a" + " répondu par une erreur, le fichier est venu d'ailleurs)", + journal, + ) if fautes: dire("", journal) dire( diff --git a/test/test_qemu_cache_mesure.py b/test/test_qemu_cache_mesure.py index 92fdde4..e21b9b9 100644 --- a/test/test_qemu_cache_mesure.py +++ b/test/test_qemu_cache_mesure.py @@ -42,20 +42,25 @@ def charger(): QC = charger() -def ligne(url, upstream, octets=1000, issue="stored", classe="immutable"): +def ligne( + url, upstream, octets=1000, issue="stored", classe="immutable", statut=None +): """Une ligne du journal d'accès du cache. La CLASSE est ce que le filtre lit désormais : c'est le cache qui décide si un fichier est figé, et la tenir ici en dur revenait à laisser un index - se faire passer pour un paquet. + se faire passer pour un paquet. Le STATUT n'est écrit que s'il est donné. """ - return { + l = { "url": url, "upstream": upstream, "bytes": octets, "outcome": issue, "class": classe, } + if statut is not None: + l["status"] = statut + return l PAQUET_A = ( @@ -155,6 +160,31 @@ class TestVerdict(unittest.TestCase): de mesure, donc pas de succès.""" self.assertFalse(QC.verdict([ligne(PAQUET_A, True)], [], None)) + def test_un_miroir_qui_refuse_ne_fait_pas_echouer(self): + """Un miroir rangé sous un autre chemin répond 404 à chaque paquet ; le + client prend le fichier ailleurs, et le cache le sert du disque. Le + 404 repart à l'amont aux deux VM sans que rien n'ait manqué.""" + mauvais = PAQUET_A.replace("miroir.example", "autre.example/x") + premier = [ + ligne(mauvais, True, octets=153, issue="fetched", statut=404), + ligne(PAQUET_A, True, statut=200), + ] + second = [ + ligne(mauvais, True, octets=153, issue="fetched", statut=404), + ligne(PAQUET_A, False, issue="hit", statut=200), + ] + self.assertTrue( + QC.verdict(premier, second, None), + "le 404 d'un miroir a été compté comme un paquet ressorti", + ) + + def test_un_paquet_livre_deux_fois_reste_une_faute(self): + """Le refus est écarté, pas la réponse livrée : un 200 déjà vu qui + repart à l'amont fait toujours échouer.""" + premier = [ligne(PAQUET_A, True, statut=200)] + second = [ligne(PAQUET_A, True, statut=200)] + self.assertFalse(QC.verdict(premier, second, None)) + def test_le_melange(self): premier = [ligne(PAQUET_A, True), ligne(PAQUET_B, True)] second = [