feat: Refactor mcpServer.matchList config generation logic (#2207)

This commit is contained in:
Kent Dong
2025-05-26 15:26:44 +08:00
committed by GitHub
parent ec83623614
commit ffcf5df28a
17 changed files with 1332 additions and 106 deletions

View File

@@ -0,0 +1,60 @@
// Copyright (c) 2025 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mcpserver
import (
"istio.io/istio/pkg/config"
)
var (
GvkMcpServer = config.GroupVersionKind{Group: "networking.higress.io", Version: "v1alpha1", Kind: "McpServer"}
)
const (
UpstreamTypeRest string = "rest"
UpstreamTypeSSE string = "sse"
UpstreamTypeStreamable string = "streamable"
ExactMatchType string = "exact"
PrefixMatchType string = "prefix"
SuffixMatchType string = "suffix"
ContainsMatchType string = "contains"
RegexMatchType string = "regex"
)
var (
ValidUpstreamTypes = map[string]bool{
UpstreamTypeRest: true,
UpstreamTypeSSE: true,
UpstreamTypeStreamable: true,
}
ValidPathMatchTypes = map[string]bool{
ExactMatchType: true,
PrefixMatchType: true,
SuffixMatchType: true,
ContainsMatchType: true,
RegexMatchType: true,
}
)
type McpServer struct {
Name string `json:"name,omitempty"`
Domains []string `json:"domains,omitempty"`
PathMatchType string `json:"path_match_type,omitempty"`
PathMatchValue string `json:"path_match_value,omitempty"`
UpstreamType string `json:"upstream_type,omitempty"`
EnablePathRewrite bool `json:"enable_path_rewrite,omitempty"`
PathRewritePrefix string `json:"path_rewrite_prefix,omitempty"`
}

View File

@@ -0,0 +1,70 @@
// Copyright (c) 2025 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mcpserver
import (
"reflect"
"slices"
"strings"
"sync"
)
type McpServerProvider interface {
GetMcpServers() []*McpServer
}
type McpRouteProviderAware interface {
RegisterMcpServerProvider(provider McpServerProvider)
}
type McpServerCache struct {
mcpServers []*McpServer
mutex sync.RWMutex
}
func (c *McpServerCache) GetMcpServers() []*McpServer {
c.mutex.RLock()
defer c.mutex.RUnlock()
return c.mcpServers
}
// SetMcpServers sets the mcp servers and returns true if the cached list is changed
func (c *McpServerCache) SetMcpServers(mcpServers []*McpServer) bool {
c.mutex.Lock()
defer c.mutex.Unlock()
sortedMcpServers := make([]*McpServer, 0, len(mcpServers))
sortedMcpServers = append(sortedMcpServers, mcpServers...)
// Sort the mcp servers by PathMatchValue in descending order
slices.SortFunc(sortedMcpServers, func(a, b *McpServer) int {
return strings.Compare(a.Name, b.Name)
})
if len(c.mcpServers) == len(sortedMcpServers) {
changed := false
for i := range c.mcpServers {
if !reflect.DeepEqual(c.mcpServers[i], sortedMcpServers[i]) {
changed = true
break
}
}
if !changed {
return false
}
}
c.mcpServers = sortedMcpServers
return true
}

View File

@@ -0,0 +1,654 @@
// Copyright (c) 2025 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mcpserver
import (
"testing"
"github.com/google/go-cmp/cmp"
)
func TestMcpServerCache_GetSet(t *testing.T) {
testCases := []struct {
name string
skip bool
init []*McpServer
input []*McpServer
expect []*McpServer
changed bool
}{
{
name: "nil",
init: nil,
input: nil,
changed: false,
expect: nil,
},
{
name: "nil to non-nil",
init: nil,
input: []*McpServer{
{
Name: "test2",
Domains: []string{"www.foo.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test2",
UpstreamType: UpstreamTypeSSE,
EnablePathRewrite: true,
PathRewritePrefix: "/test",
},
{
Name: "test3",
Domains: []string{"www.bar.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test3",
UpstreamType: UpstreamTypeStreamable,
EnablePathRewrite: true,
PathRewritePrefix: "/",
},
{
Name: "test1",
Domains: nil,
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test1",
UpstreamType: UpstreamTypeRest,
EnablePathRewrite: false,
PathRewritePrefix: "",
},
},
changed: true,
expect: []*McpServer{
{
Name: "test1",
Domains: nil,
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test1",
UpstreamType: UpstreamTypeRest,
EnablePathRewrite: false,
PathRewritePrefix: "",
},
{
Name: "test2",
Domains: []string{"www.foo.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test2",
UpstreamType: UpstreamTypeSSE,
EnablePathRewrite: true,
PathRewritePrefix: "/test",
},
{
Name: "test3",
Domains: []string{"www.bar.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test3",
UpstreamType: UpstreamTypeStreamable,
EnablePathRewrite: true,
PathRewritePrefix: "/",
},
},
},
{
name: "non-nil to non-nil (length increase)",
init: []*McpServer{
{
Name: "test1",
Domains: nil,
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test1",
UpstreamType: UpstreamTypeRest,
EnablePathRewrite: false,
PathRewritePrefix: "",
},
{
Name: "test2",
Domains: []string{"www.foo.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test2",
UpstreamType: UpstreamTypeSSE,
EnablePathRewrite: true,
PathRewritePrefix: "/test",
},
},
input: []*McpServer{
{
Name: "test2",
Domains: []string{"www.foo.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test2",
UpstreamType: UpstreamTypeSSE,
EnablePathRewrite: true,
PathRewritePrefix: "/test",
},
{
Name: "test3",
Domains: []string{"www.bar.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test3",
UpstreamType: UpstreamTypeStreamable,
EnablePathRewrite: true,
PathRewritePrefix: "/",
},
{
Name: "test1",
Domains: nil,
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test1",
UpstreamType: UpstreamTypeRest,
EnablePathRewrite: false,
PathRewritePrefix: "",
},
},
changed: true,
expect: []*McpServer{
{
Name: "test1",
Domains: nil,
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test1",
UpstreamType: UpstreamTypeRest,
EnablePathRewrite: false,
PathRewritePrefix: "",
},
{
Name: "test2",
Domains: []string{"www.foo.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test2",
UpstreamType: UpstreamTypeSSE,
EnablePathRewrite: true,
PathRewritePrefix: "/test",
},
{
Name: "test3",
Domains: []string{"www.bar.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test3",
UpstreamType: UpstreamTypeStreamable,
EnablePathRewrite: true,
PathRewritePrefix: "/",
},
},
},
{
name: "non-nil to non-nil (length decrease)",
init: []*McpServer{
{
Name: "test2",
Domains: []string{"www.foo.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test2",
UpstreamType: UpstreamTypeSSE,
EnablePathRewrite: true,
PathRewritePrefix: "/test",
},
{
Name: "test3",
Domains: []string{"www.bar.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test3",
UpstreamType: UpstreamTypeStreamable,
EnablePathRewrite: true,
PathRewritePrefix: "/",
},
{
Name: "test1",
Domains: nil,
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test1",
UpstreamType: UpstreamTypeRest,
EnablePathRewrite: false,
PathRewritePrefix: "",
},
},
input: []*McpServer{
{
Name: "test3",
Domains: []string{"www.bar.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test3",
UpstreamType: UpstreamTypeStreamable,
EnablePathRewrite: true,
PathRewritePrefix: "/",
},
{
Name: "test2",
Domains: []string{"www.foo.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test2",
UpstreamType: UpstreamTypeSSE,
EnablePathRewrite: true,
PathRewritePrefix: "/test",
},
},
changed: true,
expect: []*McpServer{
{
Name: "test2",
Domains: []string{"www.foo.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test2",
UpstreamType: UpstreamTypeSSE,
EnablePathRewrite: true,
PathRewritePrefix: "/test",
},
{
Name: "test3",
Domains: []string{"www.bar.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test3",
UpstreamType: UpstreamTypeStreamable,
EnablePathRewrite: true,
PathRewritePrefix: "/",
},
},
},
{
name: "non-nil to non-nil (length unchanged + name field changed)",
init: []*McpServer{
{
Name: "test2",
Domains: []string{"www.foo.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test2",
UpstreamType: UpstreamTypeSSE,
EnablePathRewrite: true,
PathRewritePrefix: "/test",
},
{
Name: "test3",
Domains: []string{"www.bar.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test3",
UpstreamType: UpstreamTypeStreamable,
EnablePathRewrite: true,
PathRewritePrefix: "/",
},
{
Name: "test1",
Domains: nil,
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test1",
UpstreamType: UpstreamTypeRest,
EnablePathRewrite: false,
PathRewritePrefix: "",
},
},
input: []*McpServer{
{
Name: "test2",
Domains: []string{"www.foo.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test2",
UpstreamType: UpstreamTypeSSE,
EnablePathRewrite: true,
PathRewritePrefix: "/test",
},
{
Name: "test3-1",
Domains: []string{"www.bar.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test3",
UpstreamType: UpstreamTypeStreamable,
EnablePathRewrite: true,
PathRewritePrefix: "/",
},
{
Name: "test1",
Domains: nil,
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test1",
UpstreamType: UpstreamTypeRest,
EnablePathRewrite: false,
PathRewritePrefix: "",
},
},
changed: true,
expect: []*McpServer{
{
Name: "test1",
Domains: nil,
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test1",
UpstreamType: UpstreamTypeRest,
EnablePathRewrite: false,
PathRewritePrefix: "",
},
{
Name: "test2",
Domains: []string{"www.foo.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test2",
UpstreamType: UpstreamTypeSSE,
EnablePathRewrite: true,
PathRewritePrefix: "/test",
},
{
Name: "test3-1",
Domains: []string{"www.bar.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test3",
UpstreamType: UpstreamTypeStreamable,
EnablePathRewrite: true,
PathRewritePrefix: "/",
},
},
},
{
name: "non-nil to non-nil (length unchanged + non-name field changed)",
init: []*McpServer{
{
Name: "test2",
Domains: []string{"www.foo.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test2",
UpstreamType: UpstreamTypeSSE,
EnablePathRewrite: true,
PathRewritePrefix: "/test",
},
{
Name: "test3",
Domains: []string{"www.bar.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test3",
UpstreamType: UpstreamTypeStreamable,
EnablePathRewrite: true,
PathRewritePrefix: "/",
},
{
Name: "test1",
Domains: nil,
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test1",
UpstreamType: UpstreamTypeRest,
EnablePathRewrite: false,
PathRewritePrefix: "",
},
},
input: []*McpServer{
{
Name: "test2",
Domains: []string{"www.foo.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test2",
UpstreamType: UpstreamTypeSSE,
EnablePathRewrite: true,
PathRewritePrefix: "/test",
},
{
Name: "test3",
Domains: []string{"www.bar-2.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test4",
UpstreamType: UpstreamTypeStreamable,
EnablePathRewrite: true,
PathRewritePrefix: "/",
},
{
Name: "test1",
Domains: nil,
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test1",
UpstreamType: UpstreamTypeRest,
EnablePathRewrite: false,
PathRewritePrefix: "",
},
},
changed: true,
expect: []*McpServer{
{
Name: "test1",
Domains: nil,
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test1",
UpstreamType: UpstreamTypeRest,
EnablePathRewrite: false,
PathRewritePrefix: "",
},
{
Name: "test2",
Domains: []string{"www.foo.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test2",
UpstreamType: UpstreamTypeSSE,
EnablePathRewrite: true,
PathRewritePrefix: "/test",
},
{
Name: "test3",
Domains: []string{"www.bar-2.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test4",
UpstreamType: UpstreamTypeStreamable,
EnablePathRewrite: true,
PathRewritePrefix: "/",
},
},
},
{
name: "non-nil to non-nil (content unchanged + order unchanged)",
init: []*McpServer{
{
Name: "test2",
Domains: []string{"www.foo.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test2",
UpstreamType: UpstreamTypeSSE,
EnablePathRewrite: true,
PathRewritePrefix: "/test",
},
{
Name: "test3",
Domains: []string{"www.bar.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test3",
UpstreamType: UpstreamTypeStreamable,
EnablePathRewrite: true,
PathRewritePrefix: "/",
},
{
Name: "test1",
Domains: nil,
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test1",
UpstreamType: UpstreamTypeRest,
EnablePathRewrite: false,
PathRewritePrefix: "",
},
},
input: []*McpServer{
{
Name: "test2",
Domains: []string{"www.foo.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test2",
UpstreamType: UpstreamTypeSSE,
EnablePathRewrite: true,
PathRewritePrefix: "/test",
},
{
Name: "test3",
Domains: []string{"www.bar.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test3",
UpstreamType: UpstreamTypeStreamable,
EnablePathRewrite: true,
PathRewritePrefix: "/",
},
{
Name: "test1",
Domains: nil,
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test1",
UpstreamType: UpstreamTypeRest,
EnablePathRewrite: false,
PathRewritePrefix: "",
},
},
changed: false,
expect: []*McpServer{
{
Name: "test1",
Domains: nil,
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test1",
UpstreamType: UpstreamTypeRest,
EnablePathRewrite: false,
PathRewritePrefix: "",
},
{
Name: "test2",
Domains: []string{"www.foo.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test2",
UpstreamType: UpstreamTypeSSE,
EnablePathRewrite: true,
PathRewritePrefix: "/test",
},
{
Name: "test3",
Domains: []string{"www.bar.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test3",
UpstreamType: UpstreamTypeStreamable,
EnablePathRewrite: true,
PathRewritePrefix: "/",
},
},
},
{
name: "non-nil to non-nil (content unchanged + order changed)",
init: []*McpServer{
{
Name: "test2",
Domains: []string{"www.foo.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test2",
UpstreamType: UpstreamTypeSSE,
EnablePathRewrite: true,
PathRewritePrefix: "/test",
},
{
Name: "test3",
Domains: []string{"www.bar.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test3",
UpstreamType: UpstreamTypeStreamable,
EnablePathRewrite: true,
PathRewritePrefix: "/",
},
{
Name: "test1",
Domains: nil,
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test1",
UpstreamType: UpstreamTypeRest,
EnablePathRewrite: false,
PathRewritePrefix: "",
},
},
input: []*McpServer{
{
Name: "test3",
Domains: []string{"www.bar.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test3",
UpstreamType: UpstreamTypeStreamable,
EnablePathRewrite: true,
PathRewritePrefix: "/",
},
{
Name: "test1",
Domains: nil,
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test1",
UpstreamType: UpstreamTypeRest,
EnablePathRewrite: false,
PathRewritePrefix: "",
},
{
Name: "test2",
Domains: []string{"www.foo.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test2",
UpstreamType: UpstreamTypeSSE,
EnablePathRewrite: true,
PathRewritePrefix: "/test",
},
},
changed: false,
expect: []*McpServer{
{
Name: "test1",
Domains: nil,
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test1",
UpstreamType: UpstreamTypeRest,
EnablePathRewrite: false,
PathRewritePrefix: "",
},
{
Name: "test2",
Domains: []string{"www.foo.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test2",
UpstreamType: UpstreamTypeSSE,
EnablePathRewrite: true,
PathRewritePrefix: "/test",
},
{
Name: "test3",
Domains: []string{"www.bar.com"},
PathMatchType: ExactMatchType,
PathMatchValue: "/mcp/test3",
UpstreamType: UpstreamTypeStreamable,
EnablePathRewrite: true,
PathRewritePrefix: "/",
},
},
},
}
for _, tt := range testCases {
if tt.skip {
continue
}
t.Run(tt.name, func(t *testing.T) {
provider := &McpServerCache{}
if provider.GetMcpServers() != nil {
t.Fatalf("GetMcpServers doesn't return nil before testing.")
}
_ = provider.SetMcpServers(tt.init)
changed := provider.SetMcpServers(tt.input)
if changed != tt.changed {
t.Fatalf("actual changed %t != expect changed %t", changed, tt.changed)
return
}
actual := provider.GetMcpServers()
if len(actual) != len(tt.expect) {
t.Fatalf("actual length %d != expect length %d", len(actual), len(tt.expect))
}
for i := range actual {
if diff := cmp.Diff(tt.expect[i], actual[i]); diff != "" {
t.Fatalf("TestMcpServerCache_GetSet() mismatch (-want +got):\n%s", diff)
}
}
})
}
}