Files
LocalAI/core/services/cluster/instance_test.go
T
Ettore Di Giacinto 6ef642f60d fix(cluster): stamp liveness on the database clock and refuse undialable addresses
DiscoverAdvertisedAddr promised to return an error rather than a fallback
no peer can dial, but only rejected an unspecified address. With PostgreSQL
on the same host or pod as a replica, which is compose, single-node and any
sidecar layout, the route to it is loopback, so every replica advertised
127.0.0.1 and a peer dialling that reached itself. Loopback, link-local and
zoned source addresses are now rejected with an error naming the remedy, and
a port outside 1-65535 is rejected before it becomes an undialable address.

Liveness was also measured on each replica's own clock: Register and
Heartbeat stamped last_seen from the Go process, and Live compared those
rows against the reading replica's time.Now(). Skew therefore shrank or
stretched the window by writerBehind+readerAhead, evicting healthy peers or
keeping dead ones. Both sides now use the database clock, which is the one
clock every replica demonstrably shares.

Assisted-by: Claude Opus 5 [claude-code]
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-08-31 22:08:42 +00:00

116 lines
3.9 KiB
Go

package cluster_test
import (
"context"
"net"
"time"
"github.com/mudler/LocalAI/core/services/cluster"
"github.com/mudler/LocalAI/core/services/testutil"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"gorm.io/gorm"
)
var _ = Describe("Instance registry", func() {
var (
db *gorm.DB
reg *cluster.Registry
ctx context.Context
)
BeforeEach(func() {
db = testutil.SetupTestDB()
Expect(db.AutoMigrate(&cluster.Instance{})).To(Succeed())
reg = cluster.NewRegistry(db)
ctx = context.Background()
})
It("registers an instance and reads it back", func() {
Expect(reg.Register(ctx, "inst-a", "10.0.0.1:8080", "v1")).To(Succeed())
got, err := reg.Get(ctx, "inst-a")
Expect(err).ToNot(HaveOccurred())
Expect(got.AdvertisedAddr).To(Equal("10.0.0.1:8080"))
Expect(got.Version).To(Equal("v1"))
})
It("re-registering the same id updates the address instead of duplicating", func() {
Expect(reg.Register(ctx, "inst-a", "10.0.0.1:8080", "v1")).To(Succeed())
Expect(reg.Register(ctx, "inst-a", "10.0.0.9:9090", "v2")).To(Succeed())
live, err := reg.Live(ctx, time.Hour)
Expect(err).ToNot(HaveOccurred())
Expect(live).To(HaveLen(1))
Expect(live[0].AdvertisedAddr).To(Equal("10.0.0.9:9090"))
})
It("reports a missing instance distinguishably", func() {
_, err := reg.Get(ctx, "nope")
Expect(err).To(MatchError(cluster.ErrInstanceNotFound))
})
It("excludes instances whose heartbeat has aged out", func() {
Expect(reg.Register(ctx, "stale", "10.0.0.1:8080", "v1")).To(Succeed())
// Age the row directly; sleeping in a spec is forbidden.
Expect(db.Model(&cluster.Instance{}).Where("id = ?", "stale").
Update("last_seen", time.Now().Add(-10*time.Minute)).Error).To(Succeed())
live, err := reg.Live(ctx, time.Minute)
Expect(err).ToNot(HaveOccurred())
Expect(live).To(BeEmpty())
})
It("brings a stale instance back with a heartbeat", func() {
Expect(reg.Register(ctx, "revive", "10.0.0.1:8080", "v1")).To(Succeed())
Expect(db.Model(&cluster.Instance{}).Where("id = ?", "revive").
Update("last_seen", time.Now().Add(-10*time.Minute)).Error).To(Succeed())
Expect(reg.Heartbeat(ctx, "revive")).To(Succeed())
live, err := reg.Live(ctx, time.Minute)
Expect(err).ToNot(HaveOccurred())
Expect(live).To(HaveLen(1))
})
It("heartbeating an unknown instance is an error, not a silent insert", func() {
Expect(reg.Heartbeat(ctx, "ghost")).To(MatchError(cluster.ErrInstanceNotFound))
})
})
var _ = Describe("Advertised address discovery", func() {
// The address itself depends on host networking and is deliberately not
// asserted. What is portable is the shape: whatever interface routes to the
// database, the port must be the one the caller asked for, not the
// database's.
It("combines a local interface with the caller's port", func() {
addr, err := cluster.DiscoverAdvertisedAddr("postgres://198.51.100.1:5432/testdb", 8080)
if err != nil {
Skip("no route to a database host on this machine: " + err.Error())
}
host, port, splitErr := net.SplitHostPort(addr)
Expect(splitErr).ToNot(HaveOccurred())
Expect(port).To(Equal("8080"))
Expect(net.ParseIP(host)).ToNot(BeNil())
})
It("refuses a DSN it cannot derive an address from", func() {
_, err := cluster.DiscoverAdvertisedAddr("", 8080)
Expect(err).To(HaveOccurred())
})
// A database on this same host routes over loopback on every platform, so
// this is deterministic rather than host-dependent. Returning 127.0.0.1
// would make a peer dialling this replica reach itself.
It("refuses a loopback route instead of advertising an address peers cannot use", func() {
addr, err := cluster.DiscoverAdvertisedAddr("postgres://user@127.0.0.1:5432/testdb", 8080)
Expect(addr).To(BeEmpty())
Expect(err).To(MatchError(ContainSubstring("loopback")))
})
It("refuses a port that cannot be dialled", func() {
_, err := cluster.DiscoverAdvertisedAddr("postgres://198.51.100.1:5432/testdb", 0)
Expect(err).To(MatchError(ContainSubstring("out of range")))
})
})