mirror of
https://github.com/alibaba/higress.git
synced 2026-06-09 12:47:28 +08:00
feat: add mcpServer in config map (#1953)
This commit is contained in:
@@ -40,6 +40,7 @@ type HigressConfig struct {
|
|||||||
Upstream *Upstream `json:"upstream,omitempty"`
|
Upstream *Upstream `json:"upstream,omitempty"`
|
||||||
DisableXEnvoyHeaders bool `json:"disableXEnvoyHeaders,omitempty"`
|
DisableXEnvoyHeaders bool `json:"disableXEnvoyHeaders,omitempty"`
|
||||||
AddXRealIpHeader bool `json:"addXRealIpHeader,omitempty"`
|
AddXRealIpHeader bool `json:"addXRealIpHeader,omitempty"`
|
||||||
|
McpServer *McpServer `json:"mcpServer,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefaultHigressConfig() *HigressConfig {
|
func NewDefaultHigressConfig() *HigressConfig {
|
||||||
@@ -51,6 +52,7 @@ func NewDefaultHigressConfig() *HigressConfig {
|
|||||||
Upstream: globalOption.Upstream,
|
Upstream: globalOption.Upstream,
|
||||||
DisableXEnvoyHeaders: globalOption.DisableXEnvoyHeaders,
|
DisableXEnvoyHeaders: globalOption.DisableXEnvoyHeaders,
|
||||||
AddXRealIpHeader: globalOption.AddXRealIpHeader,
|
AddXRealIpHeader: globalOption.AddXRealIpHeader,
|
||||||
|
McpServer: NewDefaultMcpServer(),
|
||||||
}
|
}
|
||||||
return higressConfig
|
return higressConfig
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,6 +89,9 @@ func NewConfigmapMgr(XDSUpdater model.XDSUpdater, namespace string, higressConfi
|
|||||||
globalOptionController := NewGlobalOptionController(namespace)
|
globalOptionController := NewGlobalOptionController(namespace)
|
||||||
configmapMgr.AddItemControllers(globalOptionController)
|
configmapMgr.AddItemControllers(globalOptionController)
|
||||||
|
|
||||||
|
mcpServerController := NewMcpServerController(namespace)
|
||||||
|
configmapMgr.AddItemControllers(mcpServerController)
|
||||||
|
|
||||||
configmapMgr.initEventHandlers()
|
configmapMgr.initEventHandlers()
|
||||||
|
|
||||||
return configmapMgr
|
return configmapMgr
|
||||||
|
|||||||
327
pkg/ingress/kube/configmap/mcp_server.go
Normal file
327
pkg/ingress/kube/configmap/mcp_server.go
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
// Copyright (c) 2022 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 configmap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/alibaba/higress/pkg/ingress/kube/util"
|
||||||
|
. "github.com/alibaba/higress/pkg/ingress/log"
|
||||||
|
networking "istio.io/api/networking/v1alpha3"
|
||||||
|
"istio.io/istio/pkg/config"
|
||||||
|
"istio.io/istio/pkg/config/schema/gvk"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RedisConfig defines the configuration for Redis connection
|
||||||
|
type RedisConfig struct {
|
||||||
|
// The address of Redis server in the format of "host:port"
|
||||||
|
Address string `json:"address,omitempty"`
|
||||||
|
// The username for Redis authentication
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
// The password for Redis authentication
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
// The database index to use
|
||||||
|
DB int `json:"db,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSEServer defines the configuration for Server-Sent Events (SSE) server
|
||||||
|
type SSEServer struct {
|
||||||
|
// The name of the SSE server
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
// The path where the SSE server will be mounted, the full path is (PATH + SsePathSuffix)
|
||||||
|
Path string `json:"path,omitempty"`
|
||||||
|
// The type of the SSE server
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
// Additional Config parameters for the real MCP server implementation
|
||||||
|
Config map[string]interface{} `json:"config,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// McpServer defines the configuration for MCP (Model Context Protocol) server
|
||||||
|
type McpServer struct {
|
||||||
|
// Flag to control whether MCP server is enabled
|
||||||
|
Enable bool `json:"enable,omitempty"`
|
||||||
|
// Redis Config for MCP server
|
||||||
|
Redis *RedisConfig `json:"redis,omitempty"`
|
||||||
|
// The suffix to be appended to SSE paths, default is "/sse"
|
||||||
|
SsePathSuffix string `json:"sse_path_suffix,omitempty"`
|
||||||
|
// List of SSE servers Configs
|
||||||
|
Servers []*SSEServer `json:"servers,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultMcpServer() *McpServer {
|
||||||
|
return &McpServer{Enable: false}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
higressMcpServerEnvoyFilterName = "higress-config-mcp-server"
|
||||||
|
)
|
||||||
|
|
||||||
|
func validMcpServer(m *McpServer) error {
|
||||||
|
if m == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Enable && m.Redis == nil {
|
||||||
|
return errors.New("redis config cannot be empty when mcp server is enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareMcpServer(old *McpServer, new *McpServer) (Result, error) {
|
||||||
|
if old == nil && new == nil {
|
||||||
|
return ResultNothing, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if new == nil {
|
||||||
|
return ResultDelete, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(old, new) {
|
||||||
|
return ResultReplace, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultNothing, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func deepCopyMcpServer(mcp *McpServer) (*McpServer, error) {
|
||||||
|
newMcp := NewDefaultMcpServer()
|
||||||
|
newMcp.Enable = mcp.Enable
|
||||||
|
|
||||||
|
if mcp.Redis != nil {
|
||||||
|
newMcp.Redis = &RedisConfig{
|
||||||
|
Address: mcp.Redis.Address,
|
||||||
|
Username: mcp.Redis.Username,
|
||||||
|
Password: mcp.Redis.Password,
|
||||||
|
DB: mcp.Redis.DB,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newMcp.SsePathSuffix = mcp.SsePathSuffix
|
||||||
|
|
||||||
|
if len(mcp.Servers) > 0 {
|
||||||
|
newMcp.Servers = make([]*SSEServer, len(mcp.Servers))
|
||||||
|
for i, server := range mcp.Servers {
|
||||||
|
newServer := &SSEServer{
|
||||||
|
Name: server.Name,
|
||||||
|
Path: server.Path,
|
||||||
|
Type: server.Type,
|
||||||
|
}
|
||||||
|
if server.Config != nil {
|
||||||
|
newServer.Config = make(map[string]interface{})
|
||||||
|
for k, v := range server.Config {
|
||||||
|
newServer.Config[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newMcp.Servers[i] = newServer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newMcp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type McpServerController struct {
|
||||||
|
Namespace string
|
||||||
|
mcpServer atomic.Value
|
||||||
|
Name string
|
||||||
|
eventHandler ItemEventHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMcpServerController(namespace string) *McpServerController {
|
||||||
|
mcpController := &McpServerController{
|
||||||
|
Namespace: namespace,
|
||||||
|
mcpServer: atomic.Value{},
|
||||||
|
Name: "mcpServer",
|
||||||
|
}
|
||||||
|
mcpController.SetMcpServer(NewDefaultMcpServer())
|
||||||
|
return mcpController
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *McpServerController) GetName() string {
|
||||||
|
return m.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *McpServerController) SetMcpServer(mcp *McpServer) {
|
||||||
|
m.mcpServer.Store(mcp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *McpServerController) GetMcpServer() *McpServer {
|
||||||
|
value := m.mcpServer.Load()
|
||||||
|
if value != nil {
|
||||||
|
if mcp, ok := value.(*McpServer); ok {
|
||||||
|
return mcp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *McpServerController) AddOrUpdateHigressConfig(name util.ClusterNamespacedName, old *HigressConfig, new *HigressConfig) error {
|
||||||
|
if err := validMcpServer(new.McpServer); err != nil {
|
||||||
|
IngressLog.Errorf("data:%+v convert to mcp server, error: %+v", new.McpServer, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
result, _ := compareMcpServer(old.McpServer, new.McpServer)
|
||||||
|
|
||||||
|
switch result {
|
||||||
|
case ResultReplace:
|
||||||
|
if newMcp, err := deepCopyMcpServer(new.McpServer); err != nil {
|
||||||
|
IngressLog.Infof("mcp server deepcopy error:%v", err)
|
||||||
|
} else {
|
||||||
|
m.SetMcpServer(newMcp)
|
||||||
|
IngressLog.Infof("AddOrUpdate Higress config mcp server")
|
||||||
|
m.eventHandler(higressMcpServerEnvoyFilterName)
|
||||||
|
IngressLog.Infof("send event with filter name:%s", higressMcpServerEnvoyFilterName)
|
||||||
|
}
|
||||||
|
case ResultDelete:
|
||||||
|
m.SetMcpServer(NewDefaultMcpServer())
|
||||||
|
IngressLog.Infof("Delete Higress config mcp server")
|
||||||
|
m.eventHandler(higressMcpServerEnvoyFilterName)
|
||||||
|
IngressLog.Infof("send event with filter name:%s", higressMcpServerEnvoyFilterName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *McpServerController) ValidHigressConfig(higressConfig *HigressConfig) error {
|
||||||
|
if higressConfig == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if higressConfig.McpServer == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return validMcpServer(higressConfig.McpServer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *McpServerController) RegisterItemEventHandler(eventHandler ItemEventHandler) {
|
||||||
|
m.eventHandler = eventHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *McpServerController) ConstructEnvoyFilters() ([]*config.Config, error) {
|
||||||
|
configs := make([]*config.Config, 0)
|
||||||
|
mcpServer := m.GetMcpServer()
|
||||||
|
namespace := m.Namespace
|
||||||
|
|
||||||
|
if mcpServer == nil || !mcpServer.Enable {
|
||||||
|
return configs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mcpStruct := m.constructMcpServerStruct(mcpServer)
|
||||||
|
if mcpStruct == "" {
|
||||||
|
return configs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &config.Config{
|
||||||
|
Meta: config.Meta{
|
||||||
|
GroupVersionKind: gvk.EnvoyFilter,
|
||||||
|
Name: higressMcpServerEnvoyFilterName,
|
||||||
|
Namespace: namespace,
|
||||||
|
},
|
||||||
|
Spec: &networking.EnvoyFilter{
|
||||||
|
ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
|
||||||
|
{
|
||||||
|
ApplyTo: networking.EnvoyFilter_HTTP_FILTER,
|
||||||
|
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
|
||||||
|
Context: networking.EnvoyFilter_GATEWAY,
|
||||||
|
ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{
|
||||||
|
Listener: &networking.EnvoyFilter_ListenerMatch{
|
||||||
|
FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{
|
||||||
|
Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{
|
||||||
|
Name: "envoy.filters.network.http_connection_manager",
|
||||||
|
SubFilter: &networking.EnvoyFilter_ListenerMatch_SubFilterMatch{
|
||||||
|
Name: "envoy.filters.http.cors",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Patch: &networking.EnvoyFilter_Patch{
|
||||||
|
Operation: networking.EnvoyFilter_Patch_INSERT_AFTER,
|
||||||
|
Value: util.BuildPatchStruct(mcpStruct),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
configs = append(configs, config)
|
||||||
|
return configs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *McpServerController) constructMcpServerStruct(mcp *McpServer) string {
|
||||||
|
// 构建 servers 配置
|
||||||
|
servers := "[]"
|
||||||
|
if len(mcp.Servers) > 0 {
|
||||||
|
serverConfigs := make([]string, len(mcp.Servers))
|
||||||
|
for i, server := range mcp.Servers {
|
||||||
|
serverConfig := fmt.Sprintf(`{
|
||||||
|
"name": "%s",
|
||||||
|
"path": "%s",
|
||||||
|
"type": "%s"`,
|
||||||
|
server.Name, server.Path, server.Type)
|
||||||
|
|
||||||
|
if len(server.Config) > 0 {
|
||||||
|
config, _ := json.Marshal(server.Config)
|
||||||
|
serverConfig += fmt.Sprintf(`,
|
||||||
|
"config": %s`, string(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
serverConfig += "}"
|
||||||
|
serverConfigs[i] = serverConfig
|
||||||
|
}
|
||||||
|
servers = fmt.Sprintf("[%s]", strings.Join(serverConfigs, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建完整的配置结构
|
||||||
|
structFmt := `{
|
||||||
|
"name": "envoy.filters.http.golang",
|
||||||
|
"typed_config": {
|
||||||
|
"@type": "type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.Config",
|
||||||
|
"value": {
|
||||||
|
"library_id": "mcp-server",
|
||||||
|
"library_path": "/var/lib/istio/envoy/mcp-server.so",
|
||||||
|
"plugin_name": "mcp-server",
|
||||||
|
"plugin_config": {
|
||||||
|
"@type": "type.googleapis.com/xds.type.v3.TypedStruct",
|
||||||
|
"value": {
|
||||||
|
"redis": {
|
||||||
|
"address": "%s",
|
||||||
|
"username": "%s",
|
||||||
|
"password": "%s",
|
||||||
|
"db": %d
|
||||||
|
},
|
||||||
|
"sse_path_suffix": "%s",
|
||||||
|
"servers": %s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
return fmt.Sprintf(structFmt,
|
||||||
|
mcp.Redis.Address,
|
||||||
|
mcp.Redis.Username,
|
||||||
|
mcp.Redis.Password,
|
||||||
|
mcp.Redis.DB,
|
||||||
|
mcp.SsePathSuffix,
|
||||||
|
servers)
|
||||||
|
}
|
||||||
354
pkg/ingress/kube/configmap/mcp_server_test.go
Normal file
354
pkg/ingress/kube/configmap/mcp_server_test.go
Normal file
@@ -0,0 +1,354 @@
|
|||||||
|
// Copyright (c) 2022 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 configmap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alibaba/higress/pkg/ingress/kube/util"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_validMcpServer(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
mcp *McpServer
|
||||||
|
wantErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "default",
|
||||||
|
mcp: &McpServer{
|
||||||
|
Enable: false,
|
||||||
|
},
|
||||||
|
wantErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil",
|
||||||
|
mcp: nil,
|
||||||
|
wantErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "enabled but no redis config",
|
||||||
|
mcp: &McpServer{
|
||||||
|
Enable: true,
|
||||||
|
Redis: nil,
|
||||||
|
},
|
||||||
|
wantErr: errors.New("redis config cannot be empty when mcp server is enabled"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid config with redis",
|
||||||
|
mcp: &McpServer{
|
||||||
|
Enable: true,
|
||||||
|
Redis: &RedisConfig{
|
||||||
|
Address: "localhost:6379",
|
||||||
|
Username: "default",
|
||||||
|
Password: "password",
|
||||||
|
DB: 0,
|
||||||
|
},
|
||||||
|
SsePathSuffix: "/sse",
|
||||||
|
Servers: []*SSEServer{
|
||||||
|
{
|
||||||
|
Name: "test-server",
|
||||||
|
Path: "/test",
|
||||||
|
Type: "test",
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := validMcpServer(tt.mcp)
|
||||||
|
assert.Equal(t, tt.wantErr, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_compareMcpServer(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
old *McpServer
|
||||||
|
new *McpServer
|
||||||
|
wantResult Result
|
||||||
|
wantErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "compare both nil",
|
||||||
|
old: nil,
|
||||||
|
new: nil,
|
||||||
|
wantResult: ResultNothing,
|
||||||
|
wantErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "compare result delete",
|
||||||
|
old: &McpServer{
|
||||||
|
Enable: true,
|
||||||
|
Redis: &RedisConfig{
|
||||||
|
Address: "localhost:6379",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
new: nil,
|
||||||
|
wantResult: ResultDelete,
|
||||||
|
wantErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "compare result equal",
|
||||||
|
old: &McpServer{
|
||||||
|
Enable: true,
|
||||||
|
Redis: &RedisConfig{
|
||||||
|
Address: "localhost:6379",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
new: &McpServer{
|
||||||
|
Enable: true,
|
||||||
|
Redis: &RedisConfig{
|
||||||
|
Address: "localhost:6379",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantResult: ResultNothing,
|
||||||
|
wantErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "compare result replace",
|
||||||
|
old: &McpServer{
|
||||||
|
Enable: true,
|
||||||
|
Redis: &RedisConfig{
|
||||||
|
Address: "localhost:6379",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
new: &McpServer{
|
||||||
|
Enable: true,
|
||||||
|
Redis: &RedisConfig{
|
||||||
|
Address: "redis:6379",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantResult: ResultReplace,
|
||||||
|
wantErr: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, err := compareMcpServer(tt.old, tt.new)
|
||||||
|
assert.Equal(t, tt.wantResult, result)
|
||||||
|
assert.Equal(t, tt.wantErr, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_deepCopyMcpServer(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
mcp *McpServer
|
||||||
|
wantMcp *McpServer
|
||||||
|
wantErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "deep copy with redis only",
|
||||||
|
mcp: &McpServer{
|
||||||
|
Enable: true,
|
||||||
|
Redis: &RedisConfig{
|
||||||
|
Address: "localhost:6379",
|
||||||
|
Username: "default",
|
||||||
|
Password: "password",
|
||||||
|
DB: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantMcp: &McpServer{
|
||||||
|
Enable: true,
|
||||||
|
Redis: &RedisConfig{
|
||||||
|
Address: "localhost:6379",
|
||||||
|
Username: "default",
|
||||||
|
Password: "password",
|
||||||
|
DB: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "deep copy with full config",
|
||||||
|
mcp: &McpServer{
|
||||||
|
Enable: true,
|
||||||
|
Redis: &RedisConfig{
|
||||||
|
Address: "localhost:6379",
|
||||||
|
Username: "default",
|
||||||
|
Password: "password",
|
||||||
|
DB: 0,
|
||||||
|
},
|
||||||
|
SsePathSuffix: "/sse",
|
||||||
|
Servers: []*SSEServer{
|
||||||
|
{
|
||||||
|
Name: "test-server",
|
||||||
|
Path: "/test",
|
||||||
|
Type: "test",
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantMcp: &McpServer{
|
||||||
|
Enable: true,
|
||||||
|
Redis: &RedisConfig{
|
||||||
|
Address: "localhost:6379",
|
||||||
|
Username: "default",
|
||||||
|
Password: "password",
|
||||||
|
DB: 0,
|
||||||
|
},
|
||||||
|
SsePathSuffix: "/sse",
|
||||||
|
Servers: []*SSEServer{
|
||||||
|
{
|
||||||
|
Name: "test-server",
|
||||||
|
Path: "/test",
|
||||||
|
Type: "test",
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
mcp, err := deepCopyMcpServer(tt.mcp)
|
||||||
|
assert.Equal(t, tt.wantMcp, mcp)
|
||||||
|
assert.Equal(t, tt.wantErr, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMcpServerController_AddOrUpdateHigressConfig(t *testing.T) {
|
||||||
|
eventPush := "default"
|
||||||
|
defaultHandler := func(name string) {
|
||||||
|
eventPush = "push"
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultName := util.ClusterNamespacedName{}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
old *HigressConfig
|
||||||
|
new *HigressConfig
|
||||||
|
wantErr error
|
||||||
|
wantEventPush string
|
||||||
|
wantMcp *McpServer
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "default",
|
||||||
|
old: &HigressConfig{
|
||||||
|
McpServer: NewDefaultMcpServer(),
|
||||||
|
},
|
||||||
|
new: &HigressConfig{
|
||||||
|
McpServer: NewDefaultMcpServer(),
|
||||||
|
},
|
||||||
|
wantErr: nil,
|
||||||
|
wantEventPush: "default",
|
||||||
|
wantMcp: NewDefaultMcpServer(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "replace and push - enable mcp server",
|
||||||
|
old: &HigressConfig{
|
||||||
|
McpServer: NewDefaultMcpServer(),
|
||||||
|
},
|
||||||
|
new: &HigressConfig{
|
||||||
|
McpServer: &McpServer{
|
||||||
|
Enable: true,
|
||||||
|
Redis: &RedisConfig{
|
||||||
|
Address: "localhost:6379",
|
||||||
|
Username: "default",
|
||||||
|
Password: "password",
|
||||||
|
DB: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: nil,
|
||||||
|
wantEventPush: "push",
|
||||||
|
wantMcp: &McpServer{
|
||||||
|
Enable: true,
|
||||||
|
Redis: &RedisConfig{
|
||||||
|
Address: "localhost:6379",
|
||||||
|
Username: "default",
|
||||||
|
Password: "password",
|
||||||
|
DB: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "replace and push - update config",
|
||||||
|
old: &HigressConfig{
|
||||||
|
McpServer: &McpServer{
|
||||||
|
Enable: true,
|
||||||
|
Redis: &RedisConfig{
|
||||||
|
Address: "localhost:6379",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
new: &HigressConfig{
|
||||||
|
McpServer: &McpServer{
|
||||||
|
Enable: true,
|
||||||
|
Redis: &RedisConfig{
|
||||||
|
Address: "redis:6379",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: nil,
|
||||||
|
wantEventPush: "push",
|
||||||
|
wantMcp: &McpServer{
|
||||||
|
Enable: true,
|
||||||
|
Redis: &RedisConfig{
|
||||||
|
Address: "redis:6379",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "delete and push",
|
||||||
|
old: &HigressConfig{
|
||||||
|
McpServer: &McpServer{
|
||||||
|
Enable: true,
|
||||||
|
Redis: &RedisConfig{
|
||||||
|
Address: "localhost:6379",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
new: &HigressConfig{
|
||||||
|
McpServer: nil,
|
||||||
|
},
|
||||||
|
wantErr: nil,
|
||||||
|
wantEventPush: "push",
|
||||||
|
wantMcp: NewDefaultMcpServer(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
m := NewMcpServerController("higress-system")
|
||||||
|
m.eventHandler = defaultHandler
|
||||||
|
eventPush = "default"
|
||||||
|
err := m.AddOrUpdateHigressConfig(defaultName, tt.old, tt.new)
|
||||||
|
assert.Equal(t, tt.wantEventPush, eventPush)
|
||||||
|
assert.Equal(t, tt.wantErr, err)
|
||||||
|
assert.Equal(t, tt.wantMcp, m.GetMcpServer())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user