From 3d9c4dd2c46ff898f7d3b695ff8d4b7502ddcef9 Mon Sep 17 00:00:00 2001 From: wuhua3 Date: Tue, 24 Dec 2024 13:33:08 +0800 Subject: [PATCH] support filter MotanCluster.refers before refresh LoadBalance support filter MotanCluster.refers before refresh LoadBalance support filter MotanCluster.refers before refresh LoadBalance code review --- agent_test.go | 109 ++++++++++++++++++ cluster/RefersFilter.go | 129 +++++++++++++++++++++ cluster/RefersFilter_test.go | 209 +++++++++++++++++++++++++++++++++++ cluster/motanCluster.go | 36 +++++- cluster/motanCluster_test.go | 64 +++++++++++ core/util.go | 16 +-- core/util_test.go | 6 +- default.go | 4 + manageHandler.go | 49 ++++++++ 9 files changed, 611 insertions(+), 11 deletions(-) create mode 100644 cluster/RefersFilter.go create mode 100644 cluster/RefersFilter_test.go diff --git a/agent_test.go b/agent_test.go index 893ac673..f3e988e3 100644 --- a/agent_test.go +++ b/agent_test.go @@ -6,6 +6,7 @@ import ( "flag" "fmt" _ "fmt" + "github.com/weibocom/motan-go/cluster" "github.com/weibocom/motan-go/config" "github.com/weibocom/motan-go/endpoint" vlog "github.com/weibocom/motan-go/log" @@ -16,6 +17,7 @@ import ( _ "golang.org/x/net/context" "io/ioutil" "math/rand" + "mime/multipart" "net" "net/http" "net/url" @@ -1092,3 +1094,110 @@ func TestRuntimeHandler(t *testing.T) { t.Logf("key: %s", s) } } + +func TestClusterRefersFilterHandler(t *testing.T) { + completeConfig := cluster.RefersFilterConfigList{ + { + Group: "g1", + Service: "s1", + Mode: cluster.FilterModeExclude, + Rule: "127.0", + }, + } + completeConfigExpect, _ := json.Marshal(completeConfig) + emptyConfig := &cluster.RefersFilterConfig{} + emptyConfigExpect, _ := json.Marshal(emptyConfig) + cases := []struct { + desc string + request *http.Request + assertFunc func(t *testing.T, resp *http.Response, respErr error) + }{ + { + desc: "verify initial value", + request: func() *http.Request { + req, _ := http.NewRequest("GET", "http://127.0.0.1:8002/refers/filter/get", nil) + return req + }(), + assertFunc: func(t *testing.T, resp *http.Response, respErr error) { + assert.Nil(t, respErr) + assert.Equal(t, http.StatusOK, resp.StatusCode) + bodyBytes, err := ioutil.ReadAll(resp.Body) + assert.Nil(t, err) + assert.Equal(t, fmt.Sprintf(`{"result":"ok","data":%s}`, emptyConfigExpect), string(bodyBytes)) + }, + }, + { + desc: "set empty config", + request: func() *http.Request { + payload := &bytes.Buffer{} + writer := multipart.NewWriter(payload) + _ = writer.WriteField("config", "[]") + _ = writer.Close() + req, _ := http.NewRequest("POST", "http://127.0.0.1:8002/refers/filter/set", payload) + req.Header.Set("Content-Type", writer.FormDataContentType()) + return req + }(), + assertFunc: func(t *testing.T, resp *http.Response, respErr error) { + assert.Nil(t, respErr) + assert.Equal(t, http.StatusOK, resp.StatusCode) + bodyBytes, err := ioutil.ReadAll(resp.Body) + assert.Nil(t, err) + assert.Equal(t, `{"result":"ok","data":"ok"}`, string(bodyBytes)) + }, + }, + { + desc: "get empty config", + request: func() *http.Request { + req, _ := http.NewRequest("GET", "http://127.0.0.1:8002/refers/filter/get", nil) + return req + }(), + assertFunc: func(t *testing.T, resp *http.Response, respErr error) { + assert.Nil(t, respErr) + assert.Equal(t, http.StatusOK, resp.StatusCode) + bodyBytes, err := ioutil.ReadAll(resp.Body) + assert.Nil(t, err) + assert.Equal(t, fmt.Sprintf(`{"result":"ok","data":%s}`, emptyConfigExpect), string(bodyBytes)) + }, + }, + { + desc: "set complete config", + request: func() *http.Request { + payload := &bytes.Buffer{} + writer := multipart.NewWriter(payload) + _ = writer.WriteField("config", string(completeConfigExpect)) + _ = writer.Close() + req, _ := http.NewRequest("POST", "http://127.0.0.1:8002/refers/filter/set", payload) + req.Header.Set("Content-Type", writer.FormDataContentType()) + return req + }(), + assertFunc: func(t *testing.T, resp *http.Response, respErr error) { + assert.Nil(t, respErr) + assert.Equal(t, http.StatusOK, resp.StatusCode) + bodyBytes, err := ioutil.ReadAll(resp.Body) + assert.Nil(t, err) + assert.Equal(t, `{"result":"ok","data":"ok"}`, string(bodyBytes)) + }, + }, + { + desc: "get complete config", + request: func() *http.Request { + req, _ := http.NewRequest("POST", "http://127.0.0.1:8002/refers/filter/get", nil) + return req + }(), + assertFunc: func(t *testing.T, resp *http.Response, respErr error) { + assert.Nil(t, respErr) + assert.Equal(t, http.StatusOK, resp.StatusCode) + bodyBytes, err := ioutil.ReadAll(resp.Body) + assert.Nil(t, err) + assert.Equal(t, fmt.Sprintf(`{"result":"ok","data":%s}`, emptyConfigExpect), string(bodyBytes)) + }, + }, + } + + for _, c := range cases { + t.Logf("case %s start", c.desc) + resp, err := http.DefaultClient.Do(c.request) + c.assertFunc(t, resp, err) + t.Logf("case %s finish", c.desc) + } +} diff --git a/cluster/RefersFilter.go b/cluster/RefersFilter.go new file mode 100644 index 00000000..acf3d992 --- /dev/null +++ b/cluster/RefersFilter.go @@ -0,0 +1,129 @@ +package cluster + +import ( + "fmt" + motan "github.com/weibocom/motan-go/core" + vlog "github.com/weibocom/motan-go/log" + "strings" +) + +const ( + FilterModeInclude = "include" + FilterModeExclude = "exclude" +) + +type RefersFilterConfigList []RefersFilterConfig + +type RefersFilterConfig struct { + Group string `json:"group"` + Service string `json:"service"` + Mode string `json:"mode"` + Rule string `json:"rule"` +} + +func (list RefersFilterConfigList) Verify() error { + for _, filter := range list { + if filter.Rule == "" { + return fmt.Errorf("exist empty rule in filter config") + } + if filter.Mode != FilterModeInclude && filter.Mode != FilterModeExclude { + return fmt.Errorf("invalid mode(%s) for rule(%s)", filter.Mode, filter.Rule) + } + } + return nil +} + +func (list RefersFilterConfigList) ParseRefersFilters(clusterURL *motan.URL) RefersFilter { + var rules []RefersFilterConfig + for _, filter := range list { + if filter.Group != "" && filter.Group != clusterURL.Group { + continue + } + if filter.Service != "" && filter.Service != clusterURL.Path { + continue + } + rules = append(rules, filter) + vlog.Infof("add refer filter for group: %s, service: %s, rule: %s, mode: %s", clusterURL.Group, clusterURL.Path, filter.Rule, filter.Mode) + } + if len(rules) == 0 { + return nil + } + return NewDefaultRefersFilter(rules) +} + +type RefersFilter interface { + Filter([]motan.EndPoint) []motan.EndPoint +} + +type filterRule struct { + mode string + prefixes []string +} + +func (fr filterRule) IsMatch(refer motan.EndPoint) bool { + for _, prefix := range fr.prefixes { + if strings.HasPrefix(refer.GetURL().Host, prefix) { + if fr.mode == FilterModeExclude { + vlog.Infof("filter refer: %s, rule: %s, mode: %s", refer.GetURL().GetIdentity(), prefix, fr.mode) + } + return true + } + } + return false +} + +type DefaultRefersFilter struct { + excludeRules []filterRule + includeRules []filterRule +} + +func NewDefaultRefersFilter(filterConfig []RefersFilterConfig) *DefaultRefersFilter { + var includeRules []filterRule + var excludeRules []filterRule + for _, config := range filterConfig { + rule := filterRule{ + mode: config.Mode, + prefixes: motan.TrimSplit(config.Rule, ","), + } + if config.Mode == FilterModeExclude { + excludeRules = append(excludeRules, rule) + } else { + includeRules = append(includeRules, rule) + } + } + return &DefaultRefersFilter{includeRules: includeRules, excludeRules: excludeRules} +} + +func (f *DefaultRefersFilter) Filter(refers []motan.EndPoint) []motan.EndPoint { + var newRefers []motan.EndPoint + for _, refer := range refers { + // discard refer if hit an exclude rule + excludeRefer := false + for _, excludeRule := range f.excludeRules { + if excludeRefer { + break + } + excludeRefer = excludeRule.IsMatch(refer) + } + if excludeRefer { + continue + } + // retained refer if hit an include rule + var includeRefer bool + if len(f.includeRules) == 0 { + includeRefer = true + } + for _, includeRule := range f.includeRules { + if includeRefer { + break + } + includeRefer = includeRule.IsMatch(refer) + } + if includeRefer { + newRefers = append(newRefers, refer) + } else { + vlog.Infof("no include rule hit. filter refer: %s", refer.GetURL().GetIdentity()) + } + } + return newRefers +} diff --git a/cluster/RefersFilter_test.go b/cluster/RefersFilter_test.go new file mode 100644 index 00000000..755604a7 --- /dev/null +++ b/cluster/RefersFilter_test.go @@ -0,0 +1,209 @@ +package cluster + +import ( + "encoding/json" + "fmt" + "github.com/stretchr/testify/assert" + motan "github.com/weibocom/motan-go/core" + "testing" +) + +func TestRefersFilterConfig_Verify(t *testing.T) { + cases := []struct { + configString string + err error + }{ + { + configString: `[]`, + err: nil, + }, + { + configString: `[{"mode":"invalid"}]`, + err: fmt.Errorf("exist empty rule in filter config"), + }, + { + configString: `[{"rule":"123,21","mode":"invalid"}]`, + err: fmt.Errorf("invalid mode(invalid) for rule(123,21)"), + }, + { + configString: `[{"rule":"123,21","mode":"include"}]`, + err: nil, + }, + { + configString: `[{"rule":"123,21","mode":"include","group":"group1","service":"service1"}]`, + err: nil, + }, + } + + for _, c := range cases { + var config RefersFilterConfigList + err := json.Unmarshal([]byte(c.configString), &config) + assert.Nil(t, err) + err = config.Verify() + assert.Equal(t, c.err, err) + } +} + +func TestRefersFilterConfig_ParseRefersFilters(t *testing.T) { + cases := []struct { + desc string + config RefersFilterConfigList + assertFunc func(t *testing.T, config RefersFilterConfigList) + }{ + { + desc: "empty refer filter", + config: RefersFilterConfigList{}, + assertFunc: func(t *testing.T, config RefersFilterConfigList) { + filter := config.ParseRefersFilters(&motan.URL{Group: "g1", Path: "s1"}) + assert.Nil(t, filter) + }, + }, + { + desc: "include rule", + config: RefersFilterConfigList{ + { + Mode: FilterModeInclude, + Rule: "123,1", + }, + }, + assertFunc: func(t *testing.T, config RefersFilterConfigList) { + filter := config.ParseRefersFilters(&motan.URL{Group: "g1", Path: "s1"}) + assert.NotNil(t, filter) + df := filter.(*DefaultRefersFilter) + assert.Equal(t, 1, len(df.includeRules)) + assert.Equal(t, 2, len(df.includeRules[0].prefixes)) + assert.Equal(t, 0, len(df.excludeRules)) + }, + }, + { + desc: "mix rule", + config: RefersFilterConfigList{ + { + Mode: FilterModeInclude, + Rule: "123,1", + Group: "g1", + Service: "s1", + }, + { + Rule: "123,1", + Mode: FilterModeInclude, + Group: "g2", + Service: "s2", + }, + { + Rule: "123,1", + Mode: FilterModeInclude, + Group: "g", + Service: "", + }, + { + Rule: "123,1", + Mode: FilterModeInclude, + Group: "", + Service: "s", + }, + }, + assertFunc: func(t *testing.T, config RefersFilterConfigList) { + filter1 := config.ParseRefersFilters(&motan.URL{Group: "g1", Path: "s1"}) + assert.NotNil(t, filter1) + filter2 := config.ParseRefersFilters(&motan.URL{Group: "g2", Path: "s2"}) + assert.NotNil(t, 1, filter2) + filter3 := config.ParseRefersFilters(&motan.URL{Group: "g3", Path: "s3"}) + assert.Nil(t, filter3) + filter4 := config.ParseRefersFilters(&motan.URL{Group: "g1", Path: "s4"}) + assert.Nil(t, filter4) + filter5 := config.ParseRefersFilters(&motan.URL{Group: "g", Path: "s4"}) + assert.NotNil(t, filter5) + filter6 := config.ParseRefersFilters(&motan.URL{Group: "g4", Path: "s"}) + assert.NotNil(t, filter6) + }, + }, + } + for _, c := range cases { + t.Logf("test case: %s start", c.desc) + c.assertFunc(t, c.config) + t.Logf("test case: %s finish", c.desc) + } +} + +func TestDefaultRefersFilter_Filter(t *testing.T) { + cases := []struct { + desc string + filter *DefaultRefersFilter + assertFunc func(t *testing.T, filter DefaultRefersFilter) + }{ + { + desc: "include filter", + filter: NewDefaultRefersFilter([]RefersFilterConfig{ + { + Mode: FilterModeInclude, + Rule: "123,124", + }, + }), + assertFunc: func(t *testing.T, filter DefaultRefersFilter) { + refers := []motan.EndPoint{ + &motan.TestEndPoint{URL: &motan.URL{Host: "123.1.2.0"}}, + } + newRefers := filter.Filter(refers) + assert.Equal(t, len(refers), len(newRefers)) + + refers = []motan.EndPoint{ + &motan.TestEndPoint{URL: &motan.URL{Host: "110.1.2.0"}}, + } + newRefers = filter.Filter(refers) + assert.Equal(t, 0, len(newRefers)) + }, + }, + { + desc: "exclude filter", + filter: NewDefaultRefersFilter([]RefersFilterConfig{ + { + Mode: FilterModeExclude, + Rule: "123,356", + }, + }), + assertFunc: func(t *testing.T, filter DefaultRefersFilter) { + refers := []motan.EndPoint{ + &motan.TestEndPoint{URL: &motan.URL{Host: "123.1.2.0"}}, + } + newRefers := filter.Filter(refers) + assert.Equal(t, 0, len(newRefers)) + + refers = []motan.EndPoint{ + &motan.TestEndPoint{URL: &motan.URL{Host: "121.1.2.0"}}, + } + newRefers = filter.Filter(refers) + assert.Equal(t, len(refers), len(newRefers)) + }, + }, + { + desc: "mix filter", + filter: NewDefaultRefersFilter([]RefersFilterConfig{ + { + Mode: FilterModeInclude, + Rule: "123,190", + }, + { + Mode: FilterModeExclude, + Rule: "123,145", + }, + }), + assertFunc: func(t *testing.T, filter DefaultRefersFilter) { + refers := []motan.EndPoint{ + &motan.TestEndPoint{URL: &motan.URL{Host: "123.1.2.0"}}, + &motan.TestEndPoint{URL: &motan.URL{Host: "145.1.2.0"}}, + &motan.TestEndPoint{URL: &motan.URL{Host: "190.1.2.0"}}, + &motan.TestEndPoint{URL: &motan.URL{Host: "110.1.2.0"}}, + } + newRefers := filter.Filter(refers) + assert.Equal(t, 1, len(newRefers)) + }, + }, + } + + for _, f := range cases { + t.Logf("test case: %s start", f.desc) + f.assertFunc(t, *f.filter) + t.Logf("test case: %s finish", f.desc) + } +} diff --git a/cluster/motanCluster.go b/cluster/motanCluster.go index 98b17779..37cd6ce2 100644 --- a/cluster/motanCluster.go +++ b/cluster/motanCluster.go @@ -6,12 +6,13 @@ import ( "math/rand" "regexp" "runtime" + "runtime/debug" "strings" "sync" "time" motan "github.com/weibocom/motan-go/core" - "github.com/weibocom/motan-go/log" + vlog "github.com/weibocom/motan-go/log" ) type MotanCluster struct { @@ -32,6 +33,9 @@ type MotanCluster struct { // cached identity identity motan.AtomicString + + // exclude or include some Refers before refresh LoadBalance + refersFilter RefersFilter } func (m *MotanCluster) IsAvailable() bool { @@ -121,12 +125,31 @@ func (m *MotanCluster) refresh() { newRefers = append(newRefers, e) } } + newRefers = m.filterRefers(newRefers) // shuffle endpoints list avoid to call to determine server nodes when the list is not change. newRefers = m.ShuffleEndpoints(newRefers) m.Refers = newRefers m.LoadBalance.OnRefresh(newRefers) } +func (m *MotanCluster) filterRefers(newRefers []motan.EndPoint) (refers []motan.EndPoint) { + defer motan.HandlePanic(func() { + vlog.Errorf("cluster %s filterRefers panic. %s", m.GetIdentity(), string(debug.Stack())) + }) + if m.refersFilter == nil { + return newRefers + } + refers = newRefers + refersAfterFilter := m.refersFilter.Filter(newRefers) + if len(refersAfterFilter) > 0 { + refers = refersAfterFilter + vlog.Infof("cluster %s filterRefers. before: %d, after: %d", m.GetIdentity(), len(newRefers), len(refersAfterFilter)) + } else { + vlog.Warningf("empty refers after filter, use original refers. cluster: %s", m.url.GetIdentityWithRegistry()) + } + return refers +} + func (m *MotanCluster) ShuffleEndpoints(endpoints []motan.EndPoint) []motan.EndPoint { rand.Seed(time.Now().UnixNano()) rand.Shuffle(len(endpoints), func(i, j int) { endpoints[i], endpoints[j] = endpoints[j], endpoints[i] }) @@ -405,6 +428,17 @@ func (m *MotanCluster) GetRuntimeInfo() map[string]interface{} { return info } +func (m *MotanCluster) SetRefersFilter(filter RefersFilter) { + m.notifyLock.Lock() + defer m.notifyLock.Unlock() + // has not changed + if filter == nil && m.refersFilter == nil { + return + } + m.refersFilter = filter + m.refresh() +} + const ( clusterIdcPlaceHolder = "${idc}" ) diff --git a/cluster/motanCluster_test.go b/cluster/motanCluster_test.go index 5c80b5e8..d49863a2 100644 --- a/cluster/motanCluster_test.go +++ b/cluster/motanCluster_test.go @@ -137,6 +137,70 @@ func TestNotify(t *testing.T) { assert.NotNil(t, registries) } +func TestRefersFilters(t *testing.T) { + cluster := initCluster() + urls := make([]*motan.URL, 0, 2) + urls = append(urls, &motan.URL{Host: "127.0.0.1", Port: 8001, Protocol: "test"}) + urls = append(urls, &motan.URL{Host: "110.0.0.1", Port: 8002, Protocol: "test"}) + urls = append(urls, &motan.URL{Host: "139.0.0.1", Port: 8003, Protocol: "test"}) + cluster.Notify(RegistryURL, urls) + if len(cluster.Refers) != 3 { + t.Fatalf("cluster notify-refers size not correct. expect :2, refers size:%d", len(cluster.Refers)) + } + cases := []struct { + desc string + filter RefersFilter + expectEpCount int + }{ + { + desc: "include ip prefix", + filter: func() RefersFilter { + return NewDefaultRefersFilter([]RefersFilterConfig{ + { + Mode: FilterModeInclude, + Rule: "127,110", + }, + }) + }(), + expectEpCount: 2, + }, + { + desc: "reset filter empty", + filter: nil, + expectEpCount: 3, + }, + { + desc: "exclude ip prefix", + filter: func() RefersFilter { + return NewDefaultRefersFilter([]RefersFilterConfig{ + { + Mode: FilterModeExclude, + Rule: "127,110", + }, + }) + }(), + expectEpCount: 1, + }, + { + desc: "reset empty", + filter: nil, + expectEpCount: 3, + }, + } + newRefers := make([]motan.EndPoint, 0, 32) + for _, v := range cluster.registryRefers { + for _, e := range v { + newRefers = append(newRefers, e) + } + } + for _, c := range cases { + t.Logf("test case:%s start", c.desc) + cluster.SetRefersFilter(c.filter) + assert.Equal(t, c.expectEpCount, len(cluster.filterRefers(newRefers))) + t.Logf("test case:%s finsh", c.desc) + } +} + func TestCall(t *testing.T) { cluster := initCluster() response := cluster.Call(&motan.MotanRequest{}) diff --git a/core/util.go b/core/util.go index 0ba6f4c3..ff9b2c80 100644 --- a/core/util.go +++ b/core/util.go @@ -194,21 +194,23 @@ func TrimSplit(s string, sep string) []string { if sep == "" { return strings.Split(s, sep) } - n := strings.Count(s, sep) + 1 - a := make([]string, n) - i := 0 + var a []string for { m := strings.Index(s, sep) if m < 0 { s = strings.TrimSpace(s) break } - a[i] = strings.TrimSpace(s[:m]) - i++ + temp := strings.TrimSpace(s[:m]) + if temp != "" { + a = append(a, temp) + } s = s[m+len(sep):] } - a[i] = s - return a[:i+1] + if s != "" { + a = append(a, s) + } + return a } // TrimSplitSet slices string and convert to map set diff --git a/core/util_test.go b/core/util_test.go index 2f9ab0f9..e8108aa9 100644 --- a/core/util_test.go +++ b/core/util_test.go @@ -90,12 +90,12 @@ func TestSplitTrim(t *testing.T) { {"", "", []string{}}, {"abcd", "", []string{"a", "b", "c", "d"}}, {"☺☻☹", "", []string{"☺", "☻", "☹"}}, - {"abcd", "a", []string{"", "bcd"}}, + {"abcd", "a", []string{"bcd"}}, {"abcd", "z", []string{"abcd"}}, {space + "1....2....3....4" + space, "...", []string{"1", ".2", ".3", ".4"}}, - {"☺☻☹", "☹", []string{"☺☻", ""}}, + {"☺☻☹", "☹", []string{"☺☻"}}, {"1\t " + space + "\n2\t", " ", []string{"1", "2"}}, - {"fd , fds, ,df\n, \v\ff ds ,,fd s , fds ,", ",", []string{"fd", "fds", "", "df", "f ds", "", "fd s", "fds", ""}}, + {"fd , fds, ,df\n, \v\ff ds ,,fd s , fds ,", ",", []string{"fd", "fds", "df", "f ds", "fd s", "fds"}}, } for _, tt := range splitList { ret := TrimSplit(tt.str, tt.sep) diff --git a/default.go b/default.go index ef77faf7..c6c89479 100644 --- a/default.go +++ b/default.go @@ -93,6 +93,10 @@ func GetDefaultManageHandlers() map[string]http.Handler { runtimeHandler := &RuntimeHandler{} defaultManageHandlers["/runtime/info"] = runtimeHandler + + refersFilterHandler := &RefersFilterHandler{} + defaultManageHandlers["/refers/filter/get"] = refersFilterHandler + defaultManageHandlers["/refers/filter/set"] = refersFilterHandler }) return defaultManageHandlers } diff --git a/manageHandler.go b/manageHandler.go index 73e1bad6..77e9c067 100644 --- a/manageHandler.go +++ b/manageHandler.go @@ -20,6 +20,7 @@ import ( "runtime/trace" "strconv" "strings" + "sync" "sync/atomic" "time" @@ -917,6 +918,54 @@ func (h *RuntimeHandler) addInfos(info map[string]interface{}, key string, resul } } +type RefersFilterHandler struct { + lock sync.Mutex + agent *Agent + Config cluster.RefersFilterConfig +} + +func (h *RefersFilterHandler) SetAgent(agent *Agent) { + h.agent = agent +} + +func (h *RefersFilterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/refers/filter/get": + h.lock.Lock() + defer h.lock.Unlock() + JSON(w, "", "ok", h.Config) + case "/refers/filter/set": + h.lock.Lock() + defer h.lock.Unlock() + var config cluster.RefersFilterConfigList + configContent := r.FormValue("config") + if configContent == "" { + JSONError(w, "empty config") + return + } + err := json.Unmarshal([]byte(configContent), &config) + if err != nil { + JSONError(w, "parse config failed") + return + } + err = config.Verify() + if err != nil { + JSONError(w, err.Error()) + } + h.agent.clusterMap.Range(func(k, v interface{}) bool { + cls, ok := v.(*cluster.MotanCluster) + if !ok { + return true + } + url := cls.GetURL() + refersFilters := config.ParseRefersFilters(url) + cls.SetRefersFilter(refersFilters) + return true + }) + JSONSuccess(w, "ok") + } +} + // JSONSuccess return success JSON data func JSONSuccess(w http.ResponseWriter, data interface{}) { JSON(w, "", "ok", data)