mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-14 18:58:43 +02:00
Façade Rust, lot 3 : la supervision du moteur passe à loki-rs
En conteneur, start/stop/restart/status du moteur passent par `loki-rs engine <action>`, port à l'identique de la supervision Go : `loki serve` lancé chef de session, fichier PID et journal aux mêmes chemins (serviceIsActive, l'UI et `loki logs` lisent les mêmes fichiers), contrôle à 2 s avec la fin du journal en cas d'échec, arrêt de tout l'arbre — session, groupe et filiation, pour le moteur de Strata qui prend sa propre session — avec les mêmes délais (SIGTERM, 4 s, SIGKILL, 20 s). Le Go garde ses garde-fous (optimisation en cours, preset externe) puis délègue au loki-rs voisin de son binaire ; la dernière ligne d'erreur du superviseur devient l'erreur montrée par l'UI. LOKI_ENGINE_SUPERVISOR=go revient à l'ancienne supervision. `loki serve`, qui construit la ligne de commande du moteur, ne change pas : il aura son propre lot. Le contrôle « démarré ? » regarde si l'enfant est sorti plutôt que le signal 0 : un enfant mort reste un zombie tant que son parent vit, et un zombie répond encore au signal 0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d4fa4488cc
commit
a672760f3c
9 files changed
+808
-2
No files matched your search
@@ -38,6 +38,12 @@ func pidFilePath() string { return filepath.Join(LokiHome(), serviceName()+".pid
|
||||
func logFilePath() string { return filepath.Join(LokiHome(), serviceName()+".log") }
|
||||
|
||||
func userSvcAction(action string) error {
|
||||
// Supervision confiée à loki-rs quand il est là (sys_service_rust_linux.go).
|
||||
if rs := rustEngineSupervisor(); rs != "" {
|
||||
if handled, err := rustUserSvcAction(rs, action); handled {
|
||||
return err
|
||||
}
|
||||
}
|
||||
switch action {
|
||||
case "start":
|
||||
return userSvcStart()
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
//go:build linux
|
||||
|
||||
package loki
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Supervision du moteur déléguée à la façade Rust (lot 3 de la migration,
|
||||
// rust/loki-rs/src/engine). En conteneur, start/stop/restart/status passent
|
||||
// par `loki-rs engine <action>` : mêmes fichiers PID et journal, même arrêt de
|
||||
// tout l'arbre, mêmes délais. Les garde-fous restent ici, en amont
|
||||
// (serviceAction : optimisation en cours ; userSvcAction : preset externe) ;
|
||||
// `loki serve` construit toujours la ligne de commande du moteur.
|
||||
//
|
||||
// Repli sur la supervision Go si loki-rs est absent à côté de ce binaire, ou
|
||||
// si LOKI_ENGINE_SUPERVISOR=go (porte de sortie).
|
||||
|
||||
// rustEngineSupervisorFor : le loki-rs voisin de exe, s'il doit superviser.
|
||||
func rustEngineSupervisorFor(exe string) string {
|
||||
if strings.EqualFold(strings.TrimSpace(os.Getenv("LOKI_ENGINE_SUPERVISOR")), "go") || exe == "" {
|
||||
return ""
|
||||
}
|
||||
p := filepath.Join(filepath.Dir(exe), "loki-rs")
|
||||
st, err := os.Stat(p)
|
||||
if err != nil || st.IsDir() || st.Mode()&0o111 == 0 {
|
||||
return ""
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func rustEngineSupervisor() string {
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return rustEngineSupervisorFor(exe)
|
||||
}
|
||||
|
||||
// rustSvcAction lance `loki-rs engine <action>`. Sa sortie s'affiche telle
|
||||
// quelle ; en cas d'échec, sa dernière ligne d'erreur devient l'erreur rendue
|
||||
// (c'est elle que l'UI montre), sans le préfixe du binaire.
|
||||
func rustSvcAction(rs, action string) error {
|
||||
self, err := os.Executable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var stderr bytes.Buffer
|
||||
cmd := exec.Command(rs, "engine", action)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = io.MultiWriter(os.Stderr, &stderr)
|
||||
cmd.Env = append(os.Environ(),
|
||||
"LOKI_GO_BIN="+self,
|
||||
"LOKI_HOME="+LokiHome(),
|
||||
"LOKI_SERVICE="+serviceName(),
|
||||
)
|
||||
if err := cmd.Run(); err != nil {
|
||||
if msg := lastErrorLine(stderr.String()); msg != "" {
|
||||
return errors.New(msg)
|
||||
}
|
||||
return fmt.Errorf("loki-rs engine %s : %w", action, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func lastErrorLine(s string) string {
|
||||
lines := strings.Split(strings.TrimSpace(s), "\n")
|
||||
last := strings.TrimSpace(lines[len(lines)-1])
|
||||
return strings.TrimSpace(strings.TrimPrefix(last, "[loki-rs engine]"))
|
||||
}
|
||||
|
||||
// rustUserSvcAction : version déléguée de userSvcAction. handled=false laisse
|
||||
// l'action au Go (logs, enable/disable).
|
||||
func rustUserSvcAction(rs, action string) (handled bool, err error) {
|
||||
switch action {
|
||||
case "start", "restart":
|
||||
// Preset externe : `loki serve` sortirait aussitôt (rien à lancer),
|
||||
// et le superviseur le prendrait pour un plantage.
|
||||
if isExternalConfig(ReadConfig()) {
|
||||
if action == "restart" {
|
||||
_ = rustSvcAction(rs, "stop")
|
||||
}
|
||||
fmt.Printf("%s preset externe actif : pas de moteur local à démarrer\n", dim("[info]"))
|
||||
return true, nil
|
||||
}
|
||||
return true, rustSvcAction(rs, action)
|
||||
case "stop", "status":
|
||||
return true, rustSvcAction(rs, action)
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
//go:build linux
|
||||
|
||||
package loki
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSuperviseurRustVoisin(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
exe := filepath.Join(dir, "loki")
|
||||
t.Setenv("LOKI_ENGINE_SUPERVISOR", "")
|
||||
if got := rustEngineSupervisorFor(exe); got != "" {
|
||||
t.Fatalf("sans loki-rs voisin : %q", got)
|
||||
}
|
||||
rs := filepath.Join(dir, "loki-rs")
|
||||
if err := os.WriteFile(rs, []byte("#!/bin/sh\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := rustEngineSupervisorFor(exe); got != "" {
|
||||
t.Fatalf("loki-rs non exécutable retenu : %q", got)
|
||||
}
|
||||
if err := os.Chmod(rs, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := rustEngineSupervisorFor(exe); got != rs {
|
||||
t.Fatalf("loki-rs voisin : %q, attendu %q", got, rs)
|
||||
}
|
||||
t.Setenv("LOKI_ENGINE_SUPERVISOR", "go")
|
||||
if got := rustEngineSupervisorFor(exe); got != "" {
|
||||
t.Fatalf("LOKI_ENGINE_SUPERVISOR=go ignoré : %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
// Le superviseur Rust est réellement appelé, avec le bon environnement, et sa
|
||||
// dernière ligne d'erreur devient l'erreur rendue à l'UI.
|
||||
func TestActionDelegueeAuSuperviseurRust(t *testing.T) {
|
||||
home := testHome(t)
|
||||
dir := t.TempDir()
|
||||
trace := filepath.Join(dir, "trace")
|
||||
rs := filepath.Join(dir, "loki-rs")
|
||||
script := "#!/bin/sh\n" +
|
||||
"echo \"$1 $2 $LOKI_HOME $LOKI_SERVICE\" > " + trace + "\n" +
|
||||
"[ \"$2\" = start ] && { echo '[loki-rs engine] service loki-engine non démarré' >&2; exit 1; }\n" +
|
||||
"exit 0\n"
|
||||
if err := os.WriteFile(rs, []byte(script), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := rustSvcAction(rs, "stop"); err != nil {
|
||||
t.Fatalf("stop : %v", err)
|
||||
}
|
||||
b, _ := os.ReadFile(trace)
|
||||
if want := "engine stop " + home + " " + serviceName() + "\n"; string(b) != want {
|
||||
t.Fatalf("appel : %q, attendu %q", b, want)
|
||||
}
|
||||
err := rustSvcAction(rs, "start")
|
||||
if err == nil || err.Error() != "service loki-engine non démarré" {
|
||||
t.Fatalf("erreur rendue : %v", err)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user