mirror of
https://github.com/mudler/LocalAI.git
synced 2026-11-03 02:54:45 -05:00
fix(connectors): preserve disabled add-on discovery (#13030)
fix(connectors): preserve discovery when add-ons are disabled Return healthy empty connector discovery for explicitly disabled add-ons while keeping missing configured gateways and unreachable nonowners unavailable. Cover offline Telegram persistence through the real connector transport and synchronize the exact lifecycle/apps test inventories. Assisted-by: nib:gpt-6.1-sol Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
1 parent
9206959564
commit
a6589e4e60
3 files changed
+75
-3
No files matched your search
@@ -16,6 +16,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/mudler/LocalAI/core/config"
|
||||
"github.com/mudler/LocalAI/core/http/auth"
|
||||
"github.com/mudler/LocalAI/core/services/addons"
|
||||
"github.com/mudler/LocalAI/core/services/addons/gateway"
|
||||
@@ -109,6 +110,71 @@ func (s *ownerConnectorSource) requests() []contract.ConnectorRequest {
|
||||
}
|
||||
|
||||
var _ = Describe("connector owner routing", func() {
|
||||
It("reports healthy empty connector kinds when add-ons are explicitly disabled", func() {
|
||||
app := &Application{applicationConfig: &config.ApplicationConfig{}}
|
||||
Expect(app.startAddons()).To(Succeed())
|
||||
Expect(app.addonGateway).To(BeNil())
|
||||
kinds, err := (connectorTransport{app}).Kinds(context.Background())
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(kinds).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("persists an offline Telegram connector with the real transport when add-ons are disabled", func() {
|
||||
ctx := context.Background()
|
||||
app := &Application{applicationConfig: &config.ApplicationConfig{}}
|
||||
Expect(app.startAddons()).To(Succeed())
|
||||
tr := connectorTransport{app}
|
||||
chatStore, err := store.NewJSON("")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
agentStore, err := agents.NewJSON("")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
svc, err := studio.New(studio.Deps{Store: chatStore, Agents: agentStore, Provider: provider.New(), Harness: studiotest.NewGateHarness(), StateDir: GinkgoT().TempDir()})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
DeferCleanup(func() { Expect(svc.Close(ctx)).To(Succeed()) })
|
||||
dir := GinkgoT().TempDir()
|
||||
bindings, err := connectors.NewJSON(dir)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
client, err := studio.NewConnectors(svc, studio.ConnectorOptions{Store: bindings, Transport: tr, VerifyToken: studio.VerifyTokenWithMe(tr)})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
DeferCleanup(client.Close)
|
||||
ag, err := svc.SaveAgent(ctx, studio.AnonymousPrincipal, agents.Agent{Name: "Offline", Model: "fake"})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
created, err := client.CreateConnector(ctx, studio.AnonymousPrincipal, studio.ConnectorInput{Kind: connectors.KindTelegram, Name: "Telegram", AgentID: ag.ID, Token: "offline-secret", AcceptAny: true})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(created.Active).To(BeFalse())
|
||||
reopened, err := connectors.NewJSON(dir)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
persisted, err := reopened.GetBinding(ctx, created.ID)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(persisted.Kind).To(Equal(connectors.KindTelegram))
|
||||
Expect(persisted.AddonID).To(Equal("telegram"))
|
||||
Expect(persisted.AgentID).To(Equal(ag.ID))
|
||||
Expect(persisted.Token).NotTo(BeEmpty())
|
||||
})
|
||||
|
||||
It("keeps configured missing gateways and unreachable nonowners unavailable", func() {
|
||||
ctx := context.Background()
|
||||
for _, app := range []*Application{
|
||||
{}, // A fixture without config is not an explicitly disabled install.
|
||||
{applicationConfig: &config.ApplicationConfig{Addons: config.AddonConfig{Enabled: true, InstanceID: "owner", OwnerID: "owner"}}},
|
||||
{applicationConfig: &config.ApplicationConfig{Addons: config.AddonConfig{Enabled: true, Frontend: true, InstanceID: "frontend", OwnerID: "owner"}}},
|
||||
} {
|
||||
_, err := (connectorTransport{app}).Kinds(ctx)
|
||||
Expect(err).To(MatchError(gateway.ErrUnavailable))
|
||||
}
|
||||
keys := filepath.Join(GinkgoT().TempDir(), "keys")
|
||||
Expect(gateway.GenerateCarrierKeys(keys, []string{"127.0.0.1"})).To(Succeed())
|
||||
server := httptest.NewServer(http.NotFoundHandler())
|
||||
server.Close()
|
||||
cfg, err := gateway.LoadCarrierConfig(keys, "frontend", strings.Replace(server.URL, "http://", "https://", 1))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
carrier, err := gateway.NewCarrier(*cfg)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
app := &Application{applicationConfig: &config.ApplicationConfig{Addons: config.AddonConfig{Enabled: true, Frontend: true}}, addonGateway: &gateway.Gateway{Forward: carrier}}
|
||||
_, err = (connectorTransport{app}).Kinds(ctx)
|
||||
Expect(err).To(MatchError(gateway.ErrUnavailable))
|
||||
})
|
||||
|
||||
It("uses real peer admission, owner authority and stored credentials through a runtime-free frontend", func() {
|
||||
ctx := context.Background()
|
||||
keys := filepath.Join(GinkgoT().TempDir(), "keys")
|
||||
|
||||
@@ -36,6 +36,9 @@ type connectorTransport struct{ a *Application }
|
||||
func (t connectorTransport) Kinds(ctx context.Context) ([]studio.ConnectorKind, error) {
|
||||
g := t.a.addonGateway
|
||||
if g == nil {
|
||||
if cfg := t.a.ApplicationConfig(); cfg != nil && !cfg.Addons.Enabled {
|
||||
return []studio.ConnectorKind{}, nil
|
||||
}
|
||||
return nil, gateway.ErrUnavailable
|
||||
}
|
||||
return g.ConnectorKinds(ctx)
|
||||
|
||||
@@ -487,7 +487,8 @@
|
||||
"refuses the upgrade when the tag moved again after the check, and installs nothing",
|
||||
"stamps the first install, keeps the time over an upgrade and stamps it again after an uninstall",
|
||||
"needs a state root for the owner only",
|
||||
"starts the enabled add-ons side by side, so the start takes the slowest ones and not the sum"
|
||||
"starts the enabled add-ons side by side, so the start takes the slowest ones and not the sum",
|
||||
"distinguishes healthy empty connector discovery from loss of runtime ownership"
|
||||
],
|
||||
"report": ".cache/addons/logs/lifecycle.json"
|
||||
},
|
||||
@@ -2569,7 +2570,8 @@
|
||||
"logs the reason behind a generic denial and leaves the response unchanged",
|
||||
"names every typed cause of a failed install in the 403 of the dispatch route",
|
||||
"records the chat with the envelope of a read and of an action while it runs",
|
||||
"sends the add-on an envelope without a new field"
|
||||
"sends the add-on an envelope without a new field",
|
||||
"distinguishes healthy empty connector discovery from loss of runtime ownership"
|
||||
],
|
||||
"report": ".cache/addons/logs/apps.json",
|
||||
"reports": [
|
||||
@@ -2658,7 +2660,8 @@
|
||||
"does not upgrade to a release the host cannot run",
|
||||
"does nothing without an update checker",
|
||||
"keeps the manual upgrade closed to a caller who is not an administrator",
|
||||
"starts the enabled add-ons side by side, so the start takes the slowest ones and not the sum"
|
||||
"starts the enabled add-ons side by side, so the start takes the slowest ones and not the sum",
|
||||
"distinguishes healthy empty connector discovery from loss of runtime ownership"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user