Skip to content

Commit 898ae3d

Browse files
authored
fix: remote plugin connect and disconnect find plugin failed (#632)
1 parent 1283387 commit 898ae3d

2 files changed

Lines changed: 121 additions & 8 deletions

File tree

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package controlpanel
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
"unsafe"
7+
8+
"github.com/google/uuid"
9+
"github.com/stretchr/testify/assert"
10+
11+
"github.com/langgenius/dify-plugin-daemon/internal/core/debugging_runtime"
12+
"github.com/langgenius/dify-plugin-daemon/internal/core/local_runtime"
13+
"github.com/langgenius/dify-plugin-daemon/internal/types/app"
14+
"github.com/langgenius/dify-plugin-daemon/pkg/entities/manifest_entities"
15+
"github.com/langgenius/dify-plugin-daemon/pkg/entities/plugin_entities"
16+
)
17+
18+
type mockNotifier struct {
19+
connected bool
20+
disconnected bool
21+
}
22+
23+
func (m *mockNotifier) OnLocalRuntimeStarting(plugin_entities.PluginUniqueIdentifier) {}
24+
func (m *mockNotifier) OnLocalRuntimeReady(*local_runtime.LocalPluginRuntime) {}
25+
func (m *mockNotifier) OnLocalRuntimeStartFailed(plugin_entities.PluginUniqueIdentifier, error) {}
26+
func (m *mockNotifier) OnLocalRuntimeStop(*local_runtime.LocalPluginRuntime) {}
27+
func (m *mockNotifier) OnLocalRuntimeStopped(*local_runtime.LocalPluginRuntime) {}
28+
func (m *mockNotifier) OnLocalRuntimeScaleUp(*local_runtime.LocalPluginRuntime, int32) {}
29+
func (m *mockNotifier) OnLocalRuntimeScaleDown(*local_runtime.LocalPluginRuntime, int32) {}
30+
func (m *mockNotifier) OnLocalRuntimeInstanceLog(*local_runtime.LocalPluginRuntime, *local_runtime.PluginInstance, plugin_entities.PluginLogEvent) {}
31+
func (m *mockNotifier) OnDebuggingRuntimeConnected(r *debugging_runtime.RemotePluginRuntime) { m.connected = true }
32+
func (m *mockNotifier) OnDebuggingRuntimeDisconnected(r *debugging_runtime.RemotePluginRuntime) { m.disconnected = true }
33+
34+
// setPrivateString sets an unexported string field on a struct value via unsafe reflection.
35+
func setPrivateString(target any, field string, value string) {
36+
rv := reflect.ValueOf(target).Elem()
37+
f := rv.FieldByName(field)
38+
reflect.NewAt(f.Type(), unsafe.Pointer(f.UnsafeAddr())).Elem().SetString(value)
39+
}
40+
41+
func newFakeRemoteRuntime(t *testing.T, name, version string) *debugging_runtime.RemotePluginRuntime {
42+
t.Helper()
43+
44+
r := &debugging_runtime.RemotePluginRuntime{
45+
PluginRuntime: plugin_entities.PluginRuntime{
46+
Config: plugin_entities.PluginDeclaration{
47+
PluginDeclarationWithoutAdvancedFields: plugin_entities.PluginDeclarationWithoutAdvancedFields{
48+
// Author is overwritten with tenantId inside Identity()
49+
Name: name,
50+
Version: manifest_entities.Version(version),
51+
},
52+
},
53+
},
54+
}
55+
56+
// Provide required tenantId and checksum so Identity() succeeds
57+
setPrivateString(r, "tenantId", uuid.New().String())
58+
setPrivateString(r, "checksum", "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
59+
60+
return r
61+
}
62+
63+
func TestOnDebuggingRuntimeConnectedAndDisconnected(t *testing.T) {
64+
cp := NewControlPanel(&app.Config{PluginLocalLaunchingConcurrent: 1}, nil, nil, nil, nil)
65+
66+
// Attach mock notifier to observe callbacks
67+
mn := &mockNotifier{}
68+
cp.AddNotifier(mn)
69+
70+
r := newFakeRemoteRuntime(t, "conn_test", "1.2.3")
71+
72+
// Connected path
73+
err := cp.onDebuggingRuntimeConnected(r)
74+
assert.NoError(t, err)
75+
76+
id, err := r.Identity()
77+
assert.NoError(t, err)
78+
79+
// Stored in runtime map and notifier called
80+
_, ok := cp.debuggingPluginRuntime.Load(id)
81+
assert.True(t, ok, "runtime should be stored on connect")
82+
assert.True(t, mn.connected, "connected notifier should be triggered")
83+
84+
// Disconnected path
85+
cp.onDebuggingRuntimeDisconnected(r)
86+
87+
_, ok = cp.debuggingPluginRuntime.Load(id)
88+
assert.False(t, ok, "runtime should be removed on disconnect")
89+
assert.True(t, mn.disconnected, "disconnected notifier should be triggered")
90+
}

internal/service/install_service/controlpanel.go

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,15 @@ func (l *InstallListener) OnDebuggingRuntimeConnected(runtime *debugging_runtime
5858
log.Error("install debugging plugin failed", "error", err)
5959
return
6060
}
61-
// Plugin already installed, fetch existing installation
62-
pluginIdentifier, err := runtime.Identity()
61+
62+
_, err := runtime.Identity()
6363
if err != nil {
6464
log.Error("failed to get plugin identity", "error", err)
6565
return
6666
}
67-
existingInstallation, fetchErr := fetchPluginInstallationByUniqueId(runtime.TenantId(), pluginIdentifier.String())
67+
decl := runtime.Configuration()
68+
pluginID := decl.Author + "/" + decl.Name
69+
existingInstallation, fetchErr := fetchPluginInstallationByPluginID(runtime.TenantId(), pluginID)
6870
if fetchErr != nil {
6971
log.Error("failed to fetch existing installation", "error", fetchErr)
7072
return
@@ -77,9 +79,30 @@ func (l *InstallListener) OnDebuggingRuntimeConnected(runtime *debugging_runtime
7779
}
7880

7981
func (l *InstallListener) OnDebuggingRuntimeDisconnected(runtime *debugging_runtime.RemotePluginRuntime) {
80-
pluginIdentifier, err := runtime.Identity()
81-
if err != nil {
82-
log.Error("failed to get plugin identity, check if your declaration is invalid", "error", err)
82+
var (
83+
pluginIdentifier plugin_entities.PluginUniqueIdentifier
84+
)
85+
86+
installationID := runtime.InstallationId()
87+
if installationID != "" {
88+
inst, err := db.GetOne[models.PluginInstallation](
89+
db.Equal("tenant_id", runtime.TenantId()),
90+
db.Equal("id", installationID),
91+
)
92+
if err == nil && inst.PluginUniqueIdentifier != "" {
93+
pluginIdentifier, _ = plugin_entities.NewPluginUniqueIdentifier(inst.PluginUniqueIdentifier)
94+
} else if err != nil && !errors.Is(err, db.ErrDatabaseNotFound) {
95+
log.Warn("failed to fetch installation for debugging runtime disconnect; falling back to runtime identity", "error", err)
96+
}
97+
}
98+
99+
if pluginIdentifier == "" {
100+
pi, err := runtime.Identity()
101+
if err != nil {
102+
log.Error("failed to get plugin identity, check if your declaration is invalid", "error", err)
103+
} else {
104+
pluginIdentifier = pi
105+
}
83106
}
84107

85108
if err := UninstallPlugin(
@@ -92,10 +115,10 @@ func (l *InstallListener) OnDebuggingRuntimeDisconnected(runtime *debugging_runt
92115
}
93116
}
94117

95-
func fetchPluginInstallationByUniqueId(tenantId string, pluginUniqueIdentifier string) (*models.PluginInstallation, error) {
118+
func fetchPluginInstallationByPluginID(tenantId string, pluginID string) (*models.PluginInstallation, error) {
96119
installation, err := db.GetOne[models.PluginInstallation](
97120
db.Equal("tenant_id", tenantId),
98-
db.Equal("plugin_unique_identifier", pluginUniqueIdentifier),
121+
db.Equal("plugin_id", pluginID),
99122
)
100123
if err != nil {
101124
return nil, err

0 commit comments

Comments
 (0)