f-50: lab: déploiement de two sur les hyperviseurs et provisionnement en un script #50

Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
This commit is contained in:
GnomeZworc 2026-10-04 17:36:15 +02:00
commit 4410f30afa
Signed by: nicolas.boufideline
GPG key ID: 4406BBBF8845D632
16 changed files with 360 additions and 64 deletions

View file

@ -125,6 +125,10 @@ func (l Lab) Up(ctx context.Context, fetcher provision.Fetcher, timeout time.Dur
errs = append(errs, fmt.Errorf("node %s: %w", n.Name, err))
continue
}
if err := l.checkServices(ctx, n); err != nil {
errs = append(errs, fmt.Errorf("node %s: %w", n.Name, err))
continue
}
fmt.Fprintf(l.Out, "%s: ready\n", n.Name)
}
return errors.Join(errs...)
@ -163,6 +167,26 @@ func (l Lab) waitReady(ctx context.Context, n topology.NodePlan) error {
}
}
func services(n topology.NodePlan) []string {
var units []string
if n.Role == topology.RoleHypervisor {
units = append(units, "agent.service")
}
if n.FRR != "" {
units = append(units, "frr.service")
}
return units
}
func (l Lab) checkServices(ctx context.Context, n topology.NodePlan) error {
for _, unit := range services(n) {
if err := l.Runner.Run(ctx, "ssh", l.sshArgs(n, true, []string{"systemctl", "is-active", "--quiet", unit})...); err != nil {
return fmt.Errorf("%s is not active: %w", unit, err)
}
}
return nil
}
func exitCode(err error) int {
var coded interface{ ExitCode() int }
if errors.As(err, &coded) {

View file

@ -34,9 +34,10 @@ func (e exitErr) ExitCode() int { return int(e) }
type fakeRunner struct {
mu sync.Mutex
calls [][]string
fail string
ssh map[string][]error
always map[string]error
fail string
ssh map[string][]error
always map[string]error
inactive string
}
func (f *fakeRunner) Run(_ context.Context, name string, args ...string) error {
@ -55,6 +56,9 @@ func (f *fakeRunner) Run(_ context.Context, name string, args ...string) error {
return os.WriteFile(private+".pub", []byte(labKey+"\n"), 0o644)
case "ssh":
port := args[indexOf(args, "-p")+1]
if args[len(args)-1] == f.inactive {
return exitErr(3)
}
answers := f.ssh[port]
if len(answers) == 0 {
return f.always[port]
@ -124,7 +128,7 @@ nodes:
}
const switchLast = ` rr1: { role: rr, image: deb, cpus: 1, memory: 1024, segments: [underlay] }
hv1: { role: hypervisor, image: deb, cpus: 4, memory: 16384, segments: [underlay] }
hv1: { role: hypervisor, image: deb, cpus: 4, memory: 16384, segments: [underlay], release: 0.2.0rc002 }
sw1: { role: switch, image: deb, cpus: 2, memory: 1024 }
`
@ -409,8 +413,8 @@ func TestUp_StartsSwitchesFirstAndWaitsForEveryNode(t *testing.T) {
}
ssh := f.runner.commands("ssh")
if len(ssh) != 3 {
t.Fatalf("%d ssh calls, want 3", len(ssh))
if len(ssh) != 4 {
t.Fatalf("%d ssh calls, want 4", len(ssh))
}
want := []string{"ssh",
"-i", filepath.Join(f.lab.RunDir, "lab_ed25519"),
@ -440,8 +444,8 @@ func TestUp_RetriesWhileSSHIsUnreachable(t *testing.T) {
if err := f.lab.Up(context.Background(), f.fetcher, time.Second); err != nil {
t.Fatalf("Up: %v", err)
}
if n := len(f.runner.commands("ssh")); n != 5 {
t.Errorf("%d ssh calls, want 5", n)
if n := len(f.runner.commands("ssh")); n != 6 {
t.Errorf("%d ssh calls, want 6", n)
}
}
@ -541,3 +545,44 @@ func TestStop_RefusesPidsThatTargetAGroup(t *testing.T) {
}
}
}
func TestUp_ChecksTheServicesOfEachRole(t *testing.T) {
f := newFixture(t, switchLast)
conf := filepath.Join(t.TempDir(), "sw1.conf")
if err := os.WriteFile(conf, []byte("hostname sw1\n"), 0o600); err != nil {
t.Fatal(err)
}
f.lab.Plan.Nodes[2].FRR = conf
if err := f.lab.Up(context.Background(), f.fetcher, time.Second); err != nil {
t.Fatalf("Up: %v", err)
}
var checks []string
for _, c := range f.runner.commands("ssh") {
if c[len(c)-4] == "systemctl" {
checks = append(checks, c[indexOf(c, "-p")+1]+" "+strings.Join(c[len(c)-4:], " "))
}
}
want := []string{
"2201 systemctl is-active --quiet agent.service",
"2202 systemctl is-active --quiet frr.service",
}
if !reflect.DeepEqual(checks, want) {
t.Errorf("checks = %q, want %q", checks, want)
}
}
func TestUp_ReportsAnInactiveServiceAndWaitsForTheOthers(t *testing.T) {
f := newFixture(t, switchLast)
f.runner.inactive = "agent.service"
err := f.lab.Up(context.Background(), f.fetcher, time.Second)
if err == nil || err.Error() != "node hv1: agent.service is not active: exit status 3" {
t.Errorf("error = %v", err)
}
if !strings.Contains(f.out.String(), "rr1: ready\n") || !strings.Contains(f.out.String(), "sw1: ready\n") || strings.Contains(f.out.String(), "hv1: ready") {
t.Errorf("output = %q", f.out.String())
}
}