f-37: add new lib for state handle #37
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
This commit is contained in:
parent
e2748f05a6
commit
3edc319969
2 changed files with 236 additions and 0 deletions
68
internal/state/state.go
Normal file
68
internal/state/state.go
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
// Package state définit les états possibles d'une ressource (VPC, subnet, VM)
|
||||||
|
// et centralise leur lecture/écriture en DB.
|
||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.g3e.fr/syonad/two/pkg/db/kv"
|
||||||
|
|
||||||
|
"github.com/dgraph-io/badger/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
type State string
|
||||||
|
|
||||||
|
const (
|
||||||
|
Creating State = "creating"
|
||||||
|
Running State = "running"
|
||||||
|
Error State = "error"
|
||||||
|
Deleting State = "deleting"
|
||||||
|
Deleted State = "deleted"
|
||||||
|
)
|
||||||
|
|
||||||
|
// All retourne tous les états valides, dans l'ordre du cycle de vie.
|
||||||
|
func All() []State {
|
||||||
|
return []State{Creating, Running, Error, Deleting, Deleted}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse convertit une valeur brute lue en DB en State.
|
||||||
|
func Parse(s string) (State, error) {
|
||||||
|
for _, valid := range All() {
|
||||||
|
if State(s) == valid {
|
||||||
|
return valid, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("unknown state %q", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanDelete indique si une ressource dans cet état peut être supprimée.
|
||||||
|
// Une ressource en cours de création ou déjà en cours de suppression ne l'est pas ;
|
||||||
|
// une ressource en erreur l'est, pour permettre le nettoyage d'un échec partiel.
|
||||||
|
func CanDelete(s State) bool {
|
||||||
|
return s == Running || s == Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTransient indique si l'état suppose une commande en cours d'exécution.
|
||||||
|
// Au démarrage de l'agent, la queue worker est vide : une ressource dans un état
|
||||||
|
// transitoire est donc orpheline.
|
||||||
|
func IsTransient(s State) bool {
|
||||||
|
return s == Creating || s == Deleting
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get lit l'état d'une ressource. prefix est la racine de la ressource,
|
||||||
|
// sans le suffixe /state (ex: "vpc/vpc-1").
|
||||||
|
func Get(db *badger.DB, prefix string) (State, error) {
|
||||||
|
raw, err := kv.GetFromDB(db, prefix+"/state")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return Parse(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set écrit l'état d'une ressource. prefix suit la même convention que Get.
|
||||||
|
func Set(db *badger.DB, prefix string, s State) error {
|
||||||
|
if _, err := Parse(string(s)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return kv.AddInDB(db, prefix+"/state", string(s))
|
||||||
|
}
|
||||||
168
internal/state/state_test.go
Normal file
168
internal/state/state_test.go
Normal file
|
|
@ -0,0 +1,168 @@
|
||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.g3e.fr/syonad/two/pkg/db/kv"
|
||||||
|
|
||||||
|
"github.com/dgraph-io/badger/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
// newTestDB ouvre une base BadgerDB dans un répertoire temporaire.
|
||||||
|
// La base est fermée automatiquement en fin de test.
|
||||||
|
func newTestDB(t *testing.T) *badger.DB {
|
||||||
|
t.Helper()
|
||||||
|
db := kv.InitDB(kv.Config{Path: t.TempDir()}, false)
|
||||||
|
t.Cleanup(func() { db.Close() })
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Parse ---
|
||||||
|
|
||||||
|
func TestParse_ValidStates(t *testing.T) {
|
||||||
|
for _, s := range All() {
|
||||||
|
got, err := Parse(string(s))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Parse(%q) a échoué : %v", s, err)
|
||||||
|
}
|
||||||
|
if got != s {
|
||||||
|
t.Errorf("Parse(%q) = %q, attendu %q", s, got, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParse_UnknownState(t *testing.T) {
|
||||||
|
// "created" et "started" sont les anciens états : ils ne doivent plus être
|
||||||
|
// acceptés une fois la migration passée.
|
||||||
|
for _, raw := range []string{"created", "started", "stopping", "", "RUNNING"} {
|
||||||
|
if _, err := Parse(raw); err == nil {
|
||||||
|
t.Errorf("Parse(%q) devrait échouer", raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- All ---
|
||||||
|
|
||||||
|
func TestAll_ContainsEveryState(t *testing.T) {
|
||||||
|
want := map[State]bool{Creating: false, Running: false, Error: false, Deleting: false, Deleted: false}
|
||||||
|
for _, s := range All() {
|
||||||
|
if _, ok := want[s]; !ok {
|
||||||
|
t.Errorf("All() retourne un état inattendu %q", s)
|
||||||
|
}
|
||||||
|
want[s] = true
|
||||||
|
}
|
||||||
|
for s, seen := range want {
|
||||||
|
if !seen {
|
||||||
|
t.Errorf("All() ne contient pas %q", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- CanDelete ---
|
||||||
|
|
||||||
|
func TestCanDelete(t *testing.T) {
|
||||||
|
cases := map[State]bool{
|
||||||
|
Running: true,
|
||||||
|
Error: true,
|
||||||
|
Creating: false,
|
||||||
|
Deleting: false,
|
||||||
|
Deleted: false,
|
||||||
|
}
|
||||||
|
for s, want := range cases {
|
||||||
|
if got := CanDelete(s); got != want {
|
||||||
|
t.Errorf("CanDelete(%q) = %v, attendu %v", s, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- IsTransient ---
|
||||||
|
|
||||||
|
func TestIsTransient(t *testing.T) {
|
||||||
|
cases := map[State]bool{
|
||||||
|
Creating: true,
|
||||||
|
Deleting: true,
|
||||||
|
Running: false,
|
||||||
|
Error: false,
|
||||||
|
Deleted: false,
|
||||||
|
}
|
||||||
|
for s, want := range cases {
|
||||||
|
if got := IsTransient(s); got != want {
|
||||||
|
t.Errorf("IsTransient(%q) = %v, attendu %v", s, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Get / Set ---
|
||||||
|
|
||||||
|
func TestSetGet_RoundTrip(t *testing.T) {
|
||||||
|
db := newTestDB(t)
|
||||||
|
|
||||||
|
if err := Set(db, "vpc/vpc-1", Creating); err != nil {
|
||||||
|
t.Fatalf("Set a échoué : %v", err)
|
||||||
|
}
|
||||||
|
got, err := Get(db, "vpc/vpc-1")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Get a échoué : %v", err)
|
||||||
|
}
|
||||||
|
if got != Creating {
|
||||||
|
t.Errorf("Get = %q, attendu %q", got, Creating)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Set(db, "vpc/vpc-1", Running); err != nil {
|
||||||
|
t.Fatalf("Set a échoué : %v", err)
|
||||||
|
}
|
||||||
|
got, err = Get(db, "vpc/vpc-1")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Get a échoué : %v", err)
|
||||||
|
}
|
||||||
|
if got != Running {
|
||||||
|
t.Errorf("Get après écrasement = %q, attendu %q", got, Running)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSet_WritesStateSuffix(t *testing.T) {
|
||||||
|
db := newTestDB(t)
|
||||||
|
|
||||||
|
if err := Set(db, "vm/vm-1", Running); err != nil {
|
||||||
|
t.Fatalf("Set a échoué : %v", err)
|
||||||
|
}
|
||||||
|
raw, err := kv.GetFromDB(db, "vm/vm-1/state")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("la clé vm/vm-1/state devrait exister : %v", err)
|
||||||
|
}
|
||||||
|
if raw != "running" {
|
||||||
|
t.Errorf("valeur en DB = %q, attendu \"running\"", raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSet_InvalidState(t *testing.T) {
|
||||||
|
db := newTestDB(t)
|
||||||
|
|
||||||
|
if err := Set(db, "vpc/vpc-1", State("created")); err == nil {
|
||||||
|
t.Error("Set devrait refuser un état inconnu")
|
||||||
|
}
|
||||||
|
if _, err := kv.GetFromDB(db, "vpc/vpc-1/state"); err == nil {
|
||||||
|
t.Error("aucune clé ne devrait être écrite pour un état invalide")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGet_MissingKey(t *testing.T) {
|
||||||
|
db := newTestDB(t)
|
||||||
|
|
||||||
|
if _, err := Get(db, "vpc/inconnu"); err == nil {
|
||||||
|
t.Error("Get devrait échouer sur une ressource absente")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGet_CorruptedValue(t *testing.T) {
|
||||||
|
db := newTestDB(t)
|
||||||
|
|
||||||
|
// Valeur écrite hors du package (ancien état, corruption) : Get doit
|
||||||
|
// remonter l'erreur plutôt que de retourner un State silencieusement vide.
|
||||||
|
if err := kv.AddInDB(db, "subnet/sn-1/state", "created"); err != nil {
|
||||||
|
t.Fatalf("préparation du test : %v", err)
|
||||||
|
}
|
||||||
|
if _, err := Get(db, "subnet/sn-1"); err == nil {
|
||||||
|
t.Error("Get devrait échouer sur une valeur non reconnue")
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue