Files
2026-03-18 13:48:44 +08:00

244 lines
5.8 KiB
Go

package cluster
import (
"testing"
"time"
"github.com/google/uuid"
"github.com/langgenius/dify-plugin-daemon/internal/core/io_tunnel/access_types"
"github.com/langgenius/dify-plugin-daemon/internal/core/plugin_manager/basic_runtime"
"github.com/langgenius/dify-plugin-daemon/pkg/entities"
"github.com/langgenius/dify-plugin-daemon/pkg/entities/manifest_entities"
"github.com/langgenius/dify-plugin-daemon/pkg/entities/plugin_entities"
)
type fakePlugin struct {
plugin_entities.PluginRuntime
basic_runtime.BasicChecksum
}
func (r *fakePlugin) InitEnvironment() error {
return nil
}
func (r *fakePlugin) Checksum() (string, error) {
return "", nil
}
func (r *fakePlugin) Identity() (plugin_entities.PluginUniqueIdentifier, error) {
return plugin_entities.PluginUniqueIdentifier(""), nil
}
func (r *fakePlugin) StartPlugin() error {
return nil
}
func (r *fakePlugin) Type() plugin_entities.PluginRuntimeType {
return plugin_entities.PLUGIN_RUNTIME_TYPE_LOCAL
}
func (r *fakePlugin) Wait() (<-chan bool, error) {
return nil, nil
}
func (r *fakePlugin) Listen(string) (*entities.Broadcast[plugin_entities.SessionMessage], error) {
return nil, nil
}
func (r *fakePlugin) Write(string, access_types.PluginAccessAction, []byte) error {
return nil
}
func getRandomPluginRuntime() fakePlugin {
return fakePlugin{
PluginRuntime: plugin_entities.PluginRuntime{
Config: plugin_entities.PluginDeclaration{
PluginDeclarationWithoutAdvancedFields: plugin_entities.PluginDeclarationWithoutAdvancedFields{
Name: uuid.New().String(),
Label: plugin_entities.I18nObject{
EnUS: "label",
},
Version: "0.0.1",
Type: manifest_entities.PluginType,
Author: "Yeuoly",
CreatedAt: time.Now(),
Plugins: plugin_entities.PluginExtensions{
Tools: []string{"test"},
},
},
},
},
}
}
func TestPluginScheduleLifetime(t *testing.T) {
plugin := getRandomPluginRuntime()
cluster, err := createSimulationCluster(1)
if err != nil {
t.Errorf("create simulation cluster failed: %v", err)
return
}
launchSimulationCluster(cluster)
defer closeSimulationCluster(cluster, t)
time.Sleep(time.Second * 1)
// add plugin to the cluster
err = cluster[0].RegisterPlugin(&plugin)
if err != nil {
t.Errorf("register plugin failed: %v", err)
return
}
identity, err := plugin.Identity()
if err != nil {
t.Errorf("get plugin identity failed: %v", err)
return
}
hashedIdentity := plugin_entities.HashedIdentity(identity.String())
nodes, err := cluster[0].FetchPluginAvailableNodesByHashedId(hashedIdentity)
if err != nil {
t.Errorf("fetch plugin available nodes failed: %v", err)
return
}
if len(nodes) != 1 {
t.Errorf("plugin not scheduled")
return
}
if nodes[0] != cluster[0].id {
t.Errorf("plugin scheduled to wrong node")
return
}
// trigger plugin stop
plugin.Stop()
// notify plugin has been stopped
if err := cluster[0].UnregisterPlugin(&plugin); err != nil {
t.Errorf("unregister plugin failed: %v", err)
return
}
// wait for the plugin to stop
time.Sleep(time.Second * 1)
// check if the plugin is stopped
nodes, err = cluster[0].FetchPluginAvailableNodesByHashedId(hashedIdentity)
if err != nil {
t.Errorf("fetch plugin available nodes failed: %v", err)
return
}
if len(nodes) != 0 {
t.Errorf("plugin not stopped")
return
}
}
func TestPluginRegisterIdempotent(t *testing.T) {
plugin := getRandomPluginRuntime()
cluster, err := createSimulationCluster(1)
if err != nil {
t.Errorf("create simulation cluster failed: %v", err)
return
}
launchSimulationCluster(cluster)
defer closeSimulationCluster(cluster, t)
// wait for cluster to be ready
time.Sleep(time.Second * 1)
// first registration should succeed
err = cluster[0].RegisterPlugin(&plugin)
if err != nil {
t.Errorf("first register plugin failed: %v", err)
return
}
identity, err := plugin.Identity()
if err != nil {
t.Errorf("get plugin identity failed: %v", err)
return
}
hashedIdentity := plugin_entities.HashedIdentity(identity.String())
// wait for plugin to be scheduled
time.Sleep(time.Second * 1)
// verify plugin is registered
nodes, err := cluster[0].FetchPluginAvailableNodesByHashedId(hashedIdentity)
if err != nil {
t.Errorf("fetch plugin available nodes failed: %v", err)
return
}
if len(nodes) != 1 {
t.Errorf("plugin not scheduled after first registration")
return
}
// second registration with same identity should be idempotent (no error)
err = cluster[0].RegisterPlugin(&plugin)
if err != nil {
t.Errorf("second register plugin failed (should be idempotent): %v", err)
return
}
// verify plugin is still registered after second registration
nodes, err = cluster[0].FetchPluginAvailableNodesByHashedId(hashedIdentity)
if err != nil {
t.Errorf("fetch plugin available nodes failed after second registration: %v", err)
return
}
if len(nodes) != 1 {
t.Errorf("plugin not available after second registration")
return
}
// unregister the plugin
if err := cluster[0].UnregisterPlugin(&plugin); err != nil {
t.Errorf("unregister plugin failed: %v", err)
return
}
// verify plugin is unregistered
nodes, err = cluster[0].FetchPluginAvailableNodesByHashedId(hashedIdentity)
if err != nil {
t.Errorf("fetch plugin available nodes failed after unregister: %v", err)
return
}
if len(nodes) != 0 {
t.Errorf("plugin still available after unregister")
return
}
// registration after unregister should succeed again
err = cluster[0].RegisterPlugin(&plugin)
if err != nil {
t.Errorf("register after unregister failed: %v", err)
return
}
// verify plugin is registered again
nodes, err = cluster[0].FetchPluginAvailableNodesByHashedId(hashedIdentity)
if err != nil {
t.Errorf("fetch plugin available nodes failed after re-registration: %v", err)
return
}
if len(nodes) != 1 {
t.Errorf("plugin not available after re-registration")
return
}
}