Files
Nuxgrid 6b4274d12b
docker-publish / publish (push) Failing after 14m14s
feat: add api
2026-07-27 16:06:19 +02:00

128 lines
3.2 KiB
Go

package demos
import (
"errors"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
// ErrPoolExhausted : plus de slot libre pour au moins un service requis.
var ErrPoolExhausted = errors.New("pool de ressources externes épuisé")
// Pool : allocation des ressources externes pré-provisionnées.
type Pool interface {
// Borrow réserve un slot par service requis, de façon atomique.
// Rend ErrPoolExhausted si un service n'a plus de slot libre.
Borrow(demoID string) ([]ExternalResource, error)
// Return libère tous les slots d'une démo.
Return(demoID string) error
// FreeCount renvoie le nombre de slots libres par service.
FreeCount() (map[string]int, error)
}
// GormPool : Pool adossé à PostgreSQL via GORM (verrou transactionnel).
type GormPool struct{ db *gorm.DB }
func NewGormPool(db *gorm.DB) *GormPool { return &GormPool{db: db} }
func (p *GormPool) Borrow(demoID string) ([]ExternalResource, error) {
var borrowed []ExternalResource
err := p.db.Transaction(func(tx *gorm.DB) error {
for _, svc := range PooledServices {
var r ExternalResource
// SELECT ... FOR UPDATE SKIP LOCKED : évite la course entre deux démos.
err := tx.Clauses(clause.Locking{Strength: "UPDATE", Options: "SKIP LOCKED"}).
Where("service = ? AND status = ?", svc, ResourceFree).
First(&r).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrPoolExhausted
}
if err != nil {
return err
}
r.Status = ResourceBorrowed
r.DemoID = demoID
if err := tx.Save(&r).Error; err != nil {
return err
}
borrowed = append(borrowed, r)
}
return nil
})
if err != nil {
return nil, err
}
return borrowed, nil
}
func (p *GormPool) Return(demoID string) error {
return p.db.Model(&ExternalResource{}).
Where("demo_id = ?", demoID).
Updates(map[string]any{"status": ResourceFree, "demo_id": ""}).Error
}
func (p *GormPool) FreeCount() (map[string]int, error) {
type row struct {
Service string
N int
}
var rows []row
err := p.db.Model(&ExternalResource{}).
Select("service, count(*) as n").
Where("status = ?", ResourceFree).
Group("service").Scan(&rows).Error
if err != nil {
return nil, err
}
out := map[string]int{}
for _, svc := range PooledServices {
out[svc] = 0
}
for _, r := range rows {
out[r.Service] = r.N
}
return out, nil
}
// SeedPool crée les slots manquants pour atteindre PoolSizePerService par service.
// Idempotent : n'ajoute que ce qui manque. SecretRef pointe vers le secret manager.
func SeedPool(db *gorm.DB) error {
for _, svc := range PooledServices {
var n int64
if err := db.Model(&ExternalResource{}).Where("service = ?", svc).Count(&n).Error; err != nil {
return err
}
for i := int(n) + 1; i <= PoolSizePerService; i++ {
res := ExternalResource{
Service: svc,
Label: placeholderLabel(svc, i),
SecretRef: "secretref://" + svc + "/slot-" + itoa(i),
Status: ResourceFree,
}
if err := db.Create(&res).Error; err != nil {
return err
}
}
}
return nil
}
func placeholderLabel(svc string, i int) string {
return svc + "-slot-" + itoa(i)
}
func itoa(i int) string {
if i == 0 {
return "0"
}
var b [20]byte
pos := len(b)
for i > 0 {
pos--
b[pos] = byte('0' + i%10)
i /= 10
}
return string(b[pos:])
}