diff --git a/cluster/router/script/router.go b/cluster/router/script/router.go index 84073e15f6..055d61f55f 100644 --- a/cluster/router/script/router.go +++ b/cluster/router/script/router.go @@ -66,6 +66,46 @@ func parseRoute(routeContent string) (*global.RouterConfig, error) { return routerConfig, nil } +// SetStaticConfig applies a RouterConfig directly, bypassing YAML parsing. +// Static and dynamic rules are not merged: later Process updates replace the +// current state built here. +func (s *ScriptRouter) SetStaticConfig(cfg *global.RouterConfig) { + if cfg == nil || cfg.Scope != constant.RouterScopeApplication || cfg.Key == "" || cfg.ScriptType == "" || cfg.Script == "" { + return + } + + s.mu.Lock() + defer s.mu.Unlock() + + if s.enabled && s.scriptType != "" { + in, err := ins.GetInstances(s.scriptType) + if err != nil { + logger.Errorf("[Router][Script] GetInstances failed to destroy static config, error=%v", err) + } else { + in.Destroy(s.rawScript) + } + } + + s.scriptType = cfg.ScriptType + s.rawScript = cfg.Script + s.enabled = cfg.Enabled == nil || *cfg.Enabled + if !s.enabled { + logger.Infof("[Router][Script] static config is disabled, key=%s, type=%s", cfg.Key, cfg.ScriptType) + return + } + + in, err := ins.GetInstances(s.scriptType) + if err != nil { + logger.Errorf("[Router][Script] GetInstances failed for static config, error=%v", err) + s.enabled = false + return + } + if err = in.Compile(s.rawScript); err != nil { + s.enabled = false + logger.Errorf("[Router][Script] compile static script failed, error=%v", err) + } +} + func (s *ScriptRouter) Process(event *config_center.ConfigChangeEvent) { s.mu.Lock() defer s.mu.Unlock() diff --git a/cluster/router/script/router_test.go b/cluster/router/script/router_test.go index e9b04c13ef..ae2a074366 100644 --- a/cluster/router/script/router_test.go +++ b/cluster/router/script/router_test.go @@ -28,7 +28,9 @@ import ( import ( "dubbo.apache.org/dubbo-go/v3/common" + "dubbo.apache.org/dubbo-go/v3/common/constant" "dubbo.apache.org/dubbo-go/v3/config_center" + "dubbo.apache.org/dubbo-go/v3/global" "dubbo.apache.org/dubbo-go/v3/protocol/base" "dubbo.apache.org/dubbo-go/v3/protocol/invocation" "dubbo.apache.org/dubbo-go/v3/remoting" @@ -257,6 +259,144 @@ func TestScriptRouterProcessDelSkipsConfigBody(t *testing.T) { assert.Empty(t, s.rawScript) } +func TestScriptRouterSetStaticConfig(t *testing.T) { + staticScriptForPort := func(port string) string { + return `(function route(invokers, invocation, context) { + var result = []; + for (var i = 0; i < invokers.length; i++) { + if (invokers[i].GetURL().Port === "` + port + `") { + result.push(invokers[i]); + } + } + return result; + }(invokers, invocation, context));` + } + + t.Run("apply static script config", func(t *testing.T) { + invokers, inv, _ := getRouteCheckArgs() + s := NewScriptRouter() + s.SetStaticConfig(&global.RouterConfig{ + Scope: constant.RouterScopeApplication, + Key: "dubbo.io", + ScriptType: "javascript", + Script: staticScriptForPort("20001"), + }) + + got := s.Route(invokers, nil, inv) + assert.Len(t, got, 1) + assert.Equal(t, "20001", got[0].GetURL().Port) + }) + + t.Run("ignore disabled static script config", func(t *testing.T) { + invokers, inv, _ := getRouteCheckArgs() + enabled := false + s := NewScriptRouter() + s.SetStaticConfig(&global.RouterConfig{ + Scope: constant.RouterScopeApplication, + Key: "dubbo.io", + Enabled: &enabled, + ScriptType: "javascript", + Script: staticScriptForPort("20001"), + }) + + got := s.Route(invokers, nil, inv) + assert.True(t, checkInvokersSame(got, invokers)) + }) + + t.Run("ignore config without script", func(t *testing.T) { + invokers, inv, _ := getRouteCheckArgs() + s := NewScriptRouter() + s.SetStaticConfig(&global.RouterConfig{ + Scope: constant.RouterScopeApplication, + Key: "dubbo.io", + }) + + got := s.Route(invokers, nil, inv) + assert.True(t, checkInvokersSame(got, invokers)) + }) + + t.Run("ignore config without key", func(t *testing.T) { + invokers, inv, _ := getRouteCheckArgs() + s := NewScriptRouter() + s.SetStaticConfig(&global.RouterConfig{ + Scope: constant.RouterScopeApplication, + ScriptType: "javascript", + Script: staticScriptForPort("20001"), + }) + + got := s.Route(invokers, nil, inv) + assert.True(t, checkInvokersSame(got, invokers)) + }) + + t.Run("replace previous static script config", func(t *testing.T) { + invokers, inv, _ := getRouteCheckArgs() + s := NewScriptRouter() + s.SetStaticConfig(&global.RouterConfig{ + Scope: constant.RouterScopeApplication, + Key: "dubbo.io", + ScriptType: "javascript", + Script: staticScriptForPort("20001"), + }) + s.SetStaticConfig(&global.RouterConfig{ + Scope: constant.RouterScopeApplication, + Key: "dubbo.io", + ScriptType: "javascript", + Script: staticScriptForPort("20002"), + }) + + got := s.Route(invokers, nil, inv) + assert.Len(t, got, 1) + assert.Equal(t, "20002", got[0].GetURL().Port) + }) + + t.Run("replace stale config with unsupported script type", func(t *testing.T) { + invokers, inv, _ := getRouteCheckArgs() + s := &ScriptRouter{ + enabled: true, + scriptType: "unsupported", + rawScript: "old script", + } + s.SetStaticConfig(&global.RouterConfig{ + Scope: constant.RouterScopeApplication, + Key: "dubbo.io", + ScriptType: "javascript", + Script: staticScriptForPort("20001"), + }) + + got := s.Route(invokers, nil, inv) + assert.Len(t, got, 1) + assert.Equal(t, "20001", got[0].GetURL().Port) + }) + + t.Run("disable unsupported script type", func(t *testing.T) { + invokers, inv, _ := getRouteCheckArgs() + s := NewScriptRouter() + s.SetStaticConfig(&global.RouterConfig{ + Scope: constant.RouterScopeApplication, + Key: "dubbo.io", + ScriptType: "unsupported", + Script: staticScriptForPort("20001"), + }) + + got := s.Route(invokers, nil, inv) + assert.True(t, checkInvokersSame(got, invokers)) + }) + + t.Run("disable invalid static script", func(t *testing.T) { + invokers, inv, _ := getRouteCheckArgs() + s := NewScriptRouter() + s.SetStaticConfig(&global.RouterConfig{ + Scope: constant.RouterScopeApplication, + Key: "dubbo.io", + ScriptType: "javascript", + Script: "bad input", + }) + + got := s.Route(invokers, nil, inv) + assert.True(t, checkInvokersSame(got, invokers)) + }) +} + func checkInvokersSame(invokers []base.Invoker, otherInvokers []base.Invoker) bool { k := map[string]struct{}{} for _, invoker := range otherInvokers {