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:
1 parent
ec1918b23b
commit
adef25d709
3 files changed
+24
-32
No files matched your search
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user