server add records the floating address where the summary can read it

keepalived's file holds its password and is root's alone; the control plane
runs as llmgw and could not read it, so the CONTROL line never named the
floating address.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ilker ManapandClaude Opus 5.5 committed 2026-10-05 02:19:48 +02:00
1 parent ec1918b23b
commit adef25d709
3 files changed
+24 -32

No files matched your search

+12 -25
View File
@@ -1,7 +1,6 @@
package cli
import (
"bufio"
"context"
"net"
"net/http"
@@ -15,8 +14,8 @@ import (
"gitea.mynodes.xyz/ilker/llmgw/internal/store"
)
// keepalivedPath is where `server add --vip` writes the floating address.
const keepalivedPath = "/etc/keepalived/keepalived.conf"
// floatingPath is where `server add --vip` records the floating address.
const floatingPath = "/etc/llmgw/floating"
// controlPlaneProbe answers the summary's question of which control plane
// leads: it asks each one's /leader, on the client port every one of them
@@ -46,7 +45,7 @@ func newControlPlaneProbe(st *store.DB, listen string) *controlPlaneProbe {
self, _ := os.Hostname()
return &controlPlaneProbe{st: st, self: self,
leaderURL: func(host string) string { return "http://" + net.JoinHostPort(host, port) + "/leader" },
floating: floatingAddress(keepalivedPath),
floating: floatingAddress(floatingPath),
client: &http.Client{Timeout: time.Second}}
}
@@ -104,30 +103,18 @@ func (p *controlPlaneProbe) ask(ctx context.Context, host string) string {
return "not answering"
}
// floatingAddress is the address in the keepalived configuration server add
// wrote, without its prefix; "" when there is none, or the file is someone
// else's.
// floatingAddress is the address in the file server add keeps beside the
// keepalived configuration, without its prefix; "" when nothing floats.
// keepalived's own file holds its password and is root's alone, and this
// process is not root.
func floatingAddress(path string) string {
f, err := os.Open(path) //nolint:gosec // a fixed system path
b, err := os.ReadFile(path) //nolint:gosec // a fixed path of our own
if err != nil {
return ""
}
defer f.Close()
sc := bufio.NewScanner(f)
ours, in := false, false
for sc.Scan() {
line := strings.TrimSpace(sc.Text())
switch {
case strings.HasPrefix(line, "# llmgw:"):
ours = true
case strings.HasPrefix(line, "virtual_ipaddress"):
in = true
case in && line == "}":
return ""
case in && ours && line != "":
addr, _, _ := strings.Cut(strings.Fields(line)[0], "/")
return addr
}
addr, _, _ := strings.Cut(strings.TrimSpace(string(b)), "/")
if net.ParseIP(addr) == nil {
return ""
}
return ""
return addr
}
+6 -7
View File
@@ -111,19 +111,18 @@ func TestOneControlPlaneIsNotASetOfThem(t *testing.T) {
func TestTheFloatingAddressIsReadFromWhatServerAddWrote(t *testing.T) {
dir := t.TempDir()
ours := filepath.Join(dir, "ours.conf")
if err := os.WriteFile(ours, []byte(keepalivedConf("eth0", "10.0.0.161", 24, 110, "abcd1234")), 0o600); err != nil {
file := filepath.Join(dir, "floating")
if err := os.WriteFile(file, []byte("10.0.0.161/24\n"), 0o644); err != nil {
t.Fatal(err)
}
if got := floatingAddress(ours); got != "10.0.0.161" {
if got := floatingAddress(file); got != "10.0.0.161" {
t.Errorf("read %q", got)
}
theirs := filepath.Join(dir, "theirs.conf")
if err := os.WriteFile(theirs, []byte("vrrp_instance x {\n virtual_ipaddress {\n 10.9.9.9/24\n }\n}\n"), 0o600); err != nil {
if err := os.WriteFile(file, []byte("not an address\n"), 0o644); err != nil {
t.Fatal(err)
}
if got := floatingAddress(theirs); got != "" {
t.Errorf("someone else's keepalived read as ours: %q", got)
if got := floatingAddress(file); got != "" {
t.Errorf("nonsense read as %q", got)
}
if got := floatingAddress(filepath.Join(dir, "none")); got != "" {
t.Errorf("no file read as %q", got)
+6
View File
@@ -489,6 +489,12 @@ sudo systemctl restart keepalived`, []byte(conf)); err != nil {
return fmt.Errorf("on %s: %v: %s", m.name, err, strings.TrimSpace(string(out)))
}
}
// Where the summary reads it, on both: readable, unlike keepalived's.
for _, r := range []onboard.Remote{here, sa.remote} {
if out, err := r.Run(ctx, "sudo sh -c 'cat > "+floatingPath+" && chmod 0644 "+floatingPath+"'", []byte(sa.vip+"\n")); err != nil {
return fmt.Errorf("recording it: %v: %s", err, strings.TrimSpace(string(out)))
}
}
sa.done("%s follows whichever control plane leads (keepalived on both)", sa.vip)
return nil
}