Paillasse/correcteur/libvirt.go

114 lines
3 KiB
Go
Raw Normal View History

package main
import (
"context"
"encoding/base64"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"os/exec"
"time"
)
var errDelaiDepasse = errors.New("délai dépassé")
// virsh lancé dans le conteneur nova_libvirt de Kolla. Les arguments sont
// passés tels quels (pas de shell) : rien n'est interprété sur l'hôte.
func virsh(ctx context.Context, args ...string) ([]byte, error) {
cmd := exec.CommandContext(ctx, "docker", append([]string{"exec", "nova_libvirt", "virsh"}, args...)...)
sortie, err := cmd.Output()
if err != nil {
var ee *exec.ExitError
if errors.As(err, &ee) {
return nil, fmt.Errorf("%w : %s", err, ee.Stderr)
}
return nil, err
}
return sortie, nil
}
// Métadonnées que Nova écrit dans la définition libvirt de chaque instance
// (espace de noms http://openstack.org/xmlns/libvirt/nova/…).
type domaine struct {
Metadata struct {
Instance struct {
Nom string `xml:"name"`
Owner struct {
Projet struct {
UUID string `xml:"uuid,attr"`
} `xml:"project"`
} `xml:"owner"`
} `xml:"instance"`
} `xml:"metadata"`
}
func (d domaine) nom() string { return d.Metadata.Instance.Nom }
func (d domaine) projet() string { return d.Metadata.Instance.Owner.Projet.UUID }
func lireDomaine(ctx context.Context, instance string) (domaine, error) {
var d domaine
brut, err := virsh(ctx, "dumpxml", instance)
if err != nil {
return d, err
}
err = xml.Unmarshal(brut, &d)
return d, err
}
// Lance /bin/sh -c <commande> dans la VM (guest-exec), puis interroge
// guest-exec-status jusqu'à la fin du processus.
func executer(ctx context.Context, instance, commande string) (int, string, error) {
lancement, _ := json.Marshal(map[string]any{
"execute": "guest-exec",
"arguments": map[string]any{
"path": "/bin/sh",
"arg": []string{"-c", commande},
"capture-output": true,
},
})
brut, err := virsh(ctx, "qemu-agent-command", instance, string(lancement), "--timeout", "5")
if err != nil {
return 0, "", err
}
var pid struct {
Return struct {
PID int `json:"pid"`
} `json:"return"`
}
if err := json.Unmarshal(brut, &pid); err != nil {
return 0, "", err
}
statut, _ := json.Marshal(map[string]any{"execute": "guest-exec-status", "arguments": map[string]int{"pid": pid.Return.PID}})
for {
select {
case <-ctx.Done():
return 0, "", errDelaiDepasse
case <-time.After(200 * time.Millisecond):
}
brut, err := virsh(ctx, "qemu-agent-command", instance, string(statut), "--timeout", "5")
if err != nil {
if ctx.Err() != nil {
return 0, "", errDelaiDepasse
}
return 0, "", err
}
var r struct {
Return struct {
Exited bool `json:"exited"`
ExitCode int `json:"exitcode"`
OutData string `json:"out-data"`
} `json:"return"`
}
if err := json.Unmarshal(brut, &r); err != nil {
return 0, "", err
}
if r.Return.Exited {
sortie, _ := base64.StdEncoding.DecodeString(r.Return.OutData)
return r.Return.ExitCode, string(sortie), nil
}
}
}