erplibre/script/qemu_cache/plafond_test.go
Mathieu Benoit f20e21b7d2 [ADD] cache qemu : nettoyage par âge et plafond de taille
Le cache ne diminuait jamais de lui-même et vit sur le disque de
l'orchestrateur : un parc qui déploie chaque jour finissait par le remplir,
sans que rien ne le signale. Deux règles existent désormais, éteintes par
défaut et réglées depuis le menu : l'âge du dernier usage, et un plafond de
taille qui efface le moins récemment servi jusqu'à tenir dessous, objets et
dépôts git confondus. Un minuteur systemd quotidien les applique, et une
réinstallation garde les valeurs choisies. Le menu montre d'abord ce qui
partirait.

--- EN ---

The cache never shrank by itself and lives on the orchestrator's disk: a site
deploying every day filled it up with nothing to say so. Two rules now exist,
off by default and set from the menu: the age of last use, and a size ceiling
that erases the least recently served until the cache fits under it, objects
and git mirrors alike. A daily systemd timer applies them, and a reinstall
keeps the chosen values. The menu first shows what would go.

Assisted-by: Claude Opus 5
2026-09-16 05:36:13 -04:00

136 lines
3.9 KiB
Go

// © 2026 TechnoLibre (http://www.technolibre.ca)
// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
package main
import (
"os"
"path/filepath"
"strings"
"testing"
"time"
)
func TestLireTaille(t *testing.T) {
cas := map[string]int64{
"50G": 50 << 30,
"50Gio": 50 << 30,
"50GiB": 50 << 30,
"1.5 G": 3 << 29,
"500M": 500 << 20,
"2t": 2 << 40,
"1024": 1024,
"64K": 64 << 10,
}
for brut, veut := range cas {
if got, err := LireTaille(brut); err != nil || got != veut {
t.Errorf("%q : %d (%v), attendu %d", brut, got, err, veut)
}
}
for _, brut := range []string{"", "abc", "5X", "-1G", "0"} {
if _, err := LireTaille(brut); err == nil {
t.Errorf("%q accepté", brut)
}
}
}
// objetGarde range un corps de n octets, daté d'il y a `age`, et rend sa clé.
func objetGarde(t *testing.T, s *Store, nom string, n int, age time.Duration) string {
t.Helper()
cle := Key("GET", "http://miroir.example/"+nom)
w, err := s.NewWriter(cle, Meta{URL: "http://miroir.example/" + nom, Method: "GET", Status: 200})
if err != nil {
t.Fatal(err)
}
w.Write([]byte(strings.Repeat("x", n)))
if err := w.Commit(int64(n)); err != nil {
t.Fatal(err)
}
_, corps := s.paths(cle)
quand := time.Now().Add(-age)
os.Chtimes(corps, quand, quand)
return cle
}
// Le moins récemment servi part d'abord, et la purge s'arrête dès que le
// cache tient sous le plafond : effacer plus rendrait de la place qu'on ne
// demandait pas, au prix de téléchargements repayés.
func TestLePlafondEffaceLePlusAncienDAbord(t *testing.T) {
s := &Store{Dir: t.TempDir()}
vieux := objetGarde(t, s, "vieux", 100, 90*24*time.Hour)
moyen := objetGarde(t, s, "moyen", 100, 30*24*time.Hour)
recent := objetGarde(t, s, "recent", 100, time.Hour)
b, err := PurgerJusqua(s, &GitMirror{}, 250, false)
if err != nil {
t.Fatal(err)
}
if b.Objets != 1 || b.Avant != 300 || b.Apres != 200 {
t.Errorf("bilan %+v, attendu 1 objet, 300 → 200", b)
}
if s.Detient(vieux) {
t.Error("le plus ancien est resté")
}
if !s.Detient(moyen) || !s.Detient(recent) {
t.Error("un objet plus récent est parti")
}
if metaPath, _ := s.paths(vieux); fichierExiste(metaPath) {
t.Error("le méta de l'objet effacé est resté")
}
}
func fichierExiste(p string) bool {
_, err := os.Stat(p)
return err == nil
}
func TestSousLePlafondRienNePart(t *testing.T) {
s := &Store{Dir: t.TempDir()}
cle := objetGarde(t, s, "seul", 100, 90*24*time.Hour)
b, _ := PurgerJusqua(s, &GitMirror{}, 1000, false)
if b.Objets != 0 || !s.Detient(cle) {
t.Errorf("sous le plafond, %d objet(s) effacé(s)", b.Objets)
}
}
func TestABlancRienNEstEfface(t *testing.T) {
s := &Store{Dir: t.TempDir()}
cle := objetGarde(t, s, "vieux", 100, 90*24*time.Hour)
objetGarde(t, s, "recent", 100, time.Hour)
b, _ := PurgerJusqua(s, &GitMirror{}, 150, true)
if b.Objets != 1 || b.Apres != 100 {
t.Errorf("bilan à blanc %+v, attendu 1 objet compté", b)
}
if !s.Detient(cle) {
t.Error("une purge à blanc a effacé un objet")
}
}
// Objets et dépôts se disputent le même disque : un dépôt oublié depuis des
// mois part avant des paquets servis récemment.
func TestUnDepotOublieCedeSaPlaceAvantLesObjets(t *testing.T) {
s := &Store{Dir: t.TempDir()}
objet := objetGarde(t, s, "paquet", 100, time.Hour)
m := &GitMirror{Dir: t.TempDir()}
depot := filepath.Join(m.Dir, "forge.example", "vieux.git")
if err := os.MkdirAll(depot, 0o755); err != nil {
t.Fatal(err)
}
os.WriteFile(filepath.Join(depot, "pack"), []byte(strings.Repeat("y", 500)), 0o644)
quand := time.Now().Add(-120 * 24 * time.Hour)
os.Chtimes(depot, quand, quand)
b, err := PurgerJusqua(s, m, 200, false)
if err != nil {
t.Fatal(err)
}
if b.Depots != 1 || b.Objets != 0 {
t.Errorf("bilan %+v, attendu le seul dépôt", b)
}
if fichierExiste(depot) {
t.Error("le dépôt oublié est resté")
}
if !s.Detient(objet) {
t.Error("l'objet récent est parti avant le dépôt oublié")
}
}