From 5bd20aa559600e951d18641189ac8fa790fc5ab7 Mon Sep 17 00:00:00 2001 From: EricaLiu <30773688+Erica177@users.noreply.github.com> Date: Mon, 28 Apr 2025 21:58:17 +0800 Subject: [PATCH] feat : support mcp server auto discovery for nacos registry (#2122) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 澄潭 --- go.mod | 39 +- go.sum | 144 +++- pkg/config/envs.go | 3 +- pkg/ingress/config/ingress_config.go | 65 +- pkg/ingress/kube/configmap/controller.go | 8 + pkg/ingress/kube/configmap/global.go | 4 + pkg/ingress/kube/configmap/gzip.go | 4 + pkg/ingress/kube/configmap/mcp_server.go | 35 +- pkg/ingress/kube/configmap/tracing.go | 4 + registry/mcp_model.go | 155 ++++ registry/memory/cache.go | 94 +++ registry/nacos/mcpserver/util.go | 180 +++++ registry/nacos/mcpserver/watcher.go | 989 +++++++++++++++++++++++ registry/reconcile/reconcile.go | 25 +- registry/watcher.go | 1 + 15 files changed, 1710 insertions(+), 40 deletions(-) create mode 100644 registry/mcp_model.go create mode 100644 registry/nacos/mcpserver/util.go create mode 100644 registry/nacos/mcpserver/watcher.go diff --git a/go.mod b/go.mod index 6dc766d41..eb7a2121a 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/tidwall/gjson v1.17.0 go.uber.org/atomic v1.11.0 go.uber.org/zap v1.27.0 - golang.org/x/net v0.27.0 + golang.org/x/net v0.33.0 google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13 google.golang.org/grpc v1.59.0 google.golang.org/protobuf v1.33.0 @@ -71,7 +71,27 @@ require ( github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/alecholmes/xfccparser v0.1.0 // indirect github.com/alecthomas/participle v0.4.1 // indirect - github.com/aliyun/alibaba-cloud-sdk-go v1.61.1704 // indirect + github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 // indirect + github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect + github.com/alibabacloud-go/darabonba-array v0.1.0 // indirect + github.com/alibabacloud-go/darabonba-encode-util v0.0.2 // indirect + github.com/alibabacloud-go/darabonba-map v0.0.2 // indirect + github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10 // indirect + github.com/alibabacloud-go/darabonba-signature-util v0.0.7 // indirect + github.com/alibabacloud-go/darabonba-string v1.0.2 // indirect + github.com/alibabacloud-go/debug v1.0.1 // indirect + github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect + github.com/alibabacloud-go/kms-20160120/v3 v3.2.3 // indirect + github.com/alibabacloud-go/openapi-util v0.1.0 // indirect + github.com/alibabacloud-go/tea v1.2.2 // indirect + github.com/alibabacloud-go/tea-utils v1.4.4 // indirect + github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect + github.com/alibabacloud-go/tea-xml v1.1.3 // indirect + github.com/aliyun/alibaba-cloud-sdk-go v1.61.1800 // indirect + github.com/aliyun/alibabacloud-dkms-gcs-go-sdk v0.5.1 // indirect + github.com/aliyun/alibabacloud-dkms-transfer-go-sdk v0.1.8 // indirect + github.com/aliyun/aliyun-secretsmanager-client-go v1.1.5 // indirect + github.com/aliyun/credentials-go v1.4.3 // indirect github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect @@ -82,10 +102,12 @@ require ( github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/clbanning/mxj v1.8.4 // indirect + github.com/clbanning/mxj/v2 v2.5.5 // indirect github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 // indirect github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect github.com/coreos/go-oidc/v3 v3.6.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/deckarep/golang-set v1.7.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/docker/cli v24.0.7+incompatible // indirect github.com/docker/distribution v2.8.2+incompatible // indirect @@ -165,6 +187,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc5 // indirect github.com/openshift/api v0.0.0-20230720094506-afcbe27aec7c // indirect + github.com/orcaman/concurrent-map v0.0.0-20210501183033-44dafcb38ecc // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect @@ -182,6 +205,7 @@ require ( github.com/tetratelabs/wazero v1.7.3 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3 // indirect github.com/vbatts/tar-split v0.11.3 // indirect github.com/xlab/treeprint v1.2.0 // indirect @@ -197,14 +221,14 @@ require ( go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/oauth2 v0.13.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/term v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/term v0.27.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect @@ -250,5 +274,6 @@ replace github.com/caddyserver/certmagic => github.com/2456868764/certmagic v1.0 replace ( github.com/dubbogo/gost => github.com/johnlanni/gost v1.11.23-0.20220713132522-0967a24036c6 + github.com/nacos-group/nacos-sdk-go/v2 => github.com/luoxiner/nacos-sdk-go/v2 v2.2.9-60 golang.org/x/exp => golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 ) diff --git a/go.sum b/go.sum index 6803e17ea..75c09a7b6 100644 --- a/go.sum +++ b/go.sum @@ -683,9 +683,68 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alessio/shellescape v1.2.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= +github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 h1:eIf+iGJxdU4U9ypaUfbtOWCsZSbTb8AUHvyPrxu6mAA= +github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do24zMOGGqYVWgw0s9NtiylnJglOeEB5UJo= +github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= +github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 h1:zE8vH9C7JiZLNJJQ5OwjU9mSi4T9ef9u3BURT6LCLC8= +github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5/go.mod h1:tWnyE9AjF8J8qqLk645oUmVUnFybApTQWklQmi5tY6g= +github.com/alibabacloud-go/darabonba-array v0.1.0 h1:vR8s7b1fWAQIjEjWnuF0JiKsCvclSRTfDzZHTYqfufY= +github.com/alibabacloud-go/darabonba-array v0.1.0/go.mod h1:BLKxr0brnggqOJPqT09DFJ8g3fsDshapUD3C3aOEFaI= +github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC2NG0Ax+GpOM5gtupki31XE= +github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8= +github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc= +github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.9/go.mod h1:bb+Io8Sn2RuM3/Rpme6ll86jMyFSrD1bxeV/+v61KeU= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10 h1:GEYkMApgpKEVDn6z12DcH1EGYpDYRB8JxsazM4Rywak= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10/go.mod h1:26a14FGhZVELuz2cc2AolvW4RHmIO3/HRwsdHhaIPDE= +github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg= +github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ= +github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo= +github.com/alibabacloud-go/darabonba-string v1.0.2/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA= +github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY= +github.com/alibabacloud-go/debug v1.0.0/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc= +github.com/alibabacloud-go/debug v1.0.1 h1:MsW9SmUtbb1Fnt3ieC6NNZi6aEwrXfDksD4QA6GSbPg= +github.com/alibabacloud-go/debug v1.0.1/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc= +github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q= +github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE= +github.com/alibabacloud-go/kms-20160120/v3 v3.2.3 h1:vamGcYQFwXVqR6RWcrVTTqlIXZVsYjaA7pZbx+Xw6zw= +github.com/alibabacloud-go/kms-20160120/v3 v3.2.3/go.mod h1:3rIyughsFDLie1ut9gQJXkWkMg/NfXBCk+OtXnPu3lw= +github.com/alibabacloud-go/openapi-util v0.1.0 h1:0z75cIULkDrdEhkLWgi9tnLe+KhAFE/r5Pb3312/eAY= +github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws= +github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg= +github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= +github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= +github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= +github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= +github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= +github.com/alibabacloud-go/tea v1.2.1/go.mod h1:qbzof29bM/IFhLMtJPrgTGK3eauV5J2wSyEUo4OEmnA= +github.com/alibabacloud-go/tea v1.2.2 h1:aTsR6Rl3ANWPfqeQugPglfurloyBJY85eFy7Gc1+8oU= +github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk= +github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= +github.com/alibabacloud-go/tea-utils v1.4.4 h1:lxCDvNCdTo9FaXKKq45+4vGETQUKNOW/qKTcX9Sk53o= +github.com/alibabacloud-go/tea-utils v1.4.4/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw= +github.com/alibabacloud-go/tea-utils/v2 v2.0.3/go.mod h1:sj1PbjPodAVTqGTA3olprfeeqqmwD0A5OQz94o9EuXQ= +github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4= +github.com/alibabacloud-go/tea-utils/v2 v2.0.6/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I= +github.com/alibabacloud-go/tea-utils/v2 v2.0.7 h1:WDx5qW3Xa5ZgJ1c8NfqJkF6w+AU5wB8835UdhPr6Ax0= +github.com/alibabacloud-go/tea-utils/v2 v2.0.7/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I= +github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzYtqw7dgt0= +github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8= github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk= -github.com/aliyun/alibaba-cloud-sdk-go v1.61.1704 h1:PpfENOj/vPfhhy9N2OFRjpue0hjM5XqAp2thFmkXXIk= -github.com/aliyun/alibaba-cloud-sdk-go v1.61.1704/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU= +github.com/aliyun/alibaba-cloud-sdk-go v1.61.1800 h1:ie/8RxBOfKZWcrbYSJi2Z8uX8TcOlSMwPlEJh83OeOw= +github.com/aliyun/alibaba-cloud-sdk-go v1.61.1800/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU= +github.com/aliyun/alibabacloud-dkms-gcs-go-sdk v0.5.1 h1:nJYyoFP+aqGKgPs9JeZgS1rWQ4NndNR0Zfhh161ZltU= +github.com/aliyun/alibabacloud-dkms-gcs-go-sdk v0.5.1/go.mod h1:WzGOmFFTlUzXM03CJnHWMQ85UN6QGpOXZocCjwkiyOg= +github.com/aliyun/alibabacloud-dkms-transfer-go-sdk v0.1.8 h1:QeUdR7JF7iNCvO/81EhxEr3wDwxk4YBoYZOq6E0AjHI= +github.com/aliyun/alibabacloud-dkms-transfer-go-sdk v0.1.8/go.mod h1:xP0KIZry6i7oGPF24vhAPr1Q8vLZRcMcxtft5xDKwCU= +github.com/aliyun/aliyun-secretsmanager-client-go v1.1.5 h1:8S0mtD101RDYa0LXwdoqgN0RxdMmmJYjq8g2mk7/lQ4= +github.com/aliyun/aliyun-secretsmanager-client-go v1.1.5/go.mod h1:M19fxYz3gpm0ETnoKweYyYtqrtnVtrpKFpwsghbw+cQ= +github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw= +github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0= +github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM= +github.com/aliyun/credentials-go v1.3.10/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U= +github.com/aliyun/credentials-go v1.4.3 h1:N3iHyvHRMyOwY1+0qBLSf3hb5JFiOujVSVuEpgeGttY= +github.com/aliyun/credentials-go v1.4.3/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= @@ -755,7 +814,6 @@ github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6 github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.11-0.20170329064859-445be9e134b2/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -765,6 +823,8 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6D github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I= github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= +github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E= +github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= @@ -813,6 +873,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ= +github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= @@ -1162,8 +1224,9 @@ github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97Dwqy github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0= +github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= @@ -1371,6 +1434,8 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhn github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/luoxiner/nacos-sdk-go/v2 v2.2.9-60 h1:FA/azfz2nSkMc1XR8LeqhcAiA/2/sOMcyBGYCTUc+Cs= +github.com/luoxiner/nacos-sdk-go/v2 v2.2.9-60/go.mod h1:9FKXl6FqOiVmm72i8kADtbeK71egyG9y3uRDBg41tpQ= github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= github.com/lyft/protoc-gen-star/v2 v2.0.3/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk= @@ -1460,8 +1525,6 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nacos-group/nacos-sdk-go v1.0.8 h1:8pEm05Cdav9sQgJSv5kyvlgfz0SzFUUGI3pWX6SiSnM= github.com/nacos-group/nacos-sdk-go v1.0.8/go.mod h1:hlAPn3UdzlxIlSILAyOXKxjFSvDJ9oLzTJ9hLAK1KzA= -github.com/nacos-group/nacos-sdk-go/v2 v2.1.2 h1:A8GV6j0rw80I6tTKSav/pTpEgNECYXeFvZCsiLBWGnQ= -github.com/nacos-group/nacos-sdk-go/v2 v2.1.2/go.mod h1:ys/1adWeKXXzbNWfRNbaFlX/t6HVLWdpsNDvmoWTw0g= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= @@ -1517,6 +1580,8 @@ github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJ github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.3.0/go.mod h1:4c3sLeE8xjNqehmF5RpAFLPLJxXscc0R4l6Zg0P1tTQ= +github.com/orcaman/concurrent-map v0.0.0-20210501183033-44dafcb38ecc h1:Ak86L+yDSOzKFa7WM5bf5itSOo1e3Xh8bm5YCMUXIjQ= +github.com/orcaman/concurrent-map v0.0.0-20210501183033-44dafcb38ecc/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= @@ -1560,7 +1625,6 @@ github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3O github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -1593,7 +1657,6 @@ github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4 github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/prometheus/prometheus v0.45.0 h1:O/uG+Nw4kNxx/jDPxmjsSDd+9Ohql6E7ZSY1x5x/0KI= @@ -1643,8 +1706,9 @@ github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.1.0 h1:MkTeG1DMwsrdH7QtLXy5W+fUxWq+vmb6cLmyJ7aRtF0= +github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= @@ -1713,6 +1777,9 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -1746,6 +1813,7 @@ github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+Seva github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -1832,7 +1900,6 @@ go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0 go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= @@ -1849,7 +1916,6 @@ go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= -go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -1868,9 +1934,12 @@ golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= @@ -1882,8 +1951,13 @@ golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= @@ -1970,6 +2044,7 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -2008,8 +2083,13 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -2059,8 +2139,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -2107,6 +2187,7 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2156,7 +2237,6 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -2181,8 +2261,13 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -2195,8 +2280,13 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2214,8 +2304,11 @@ golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -2225,7 +2318,6 @@ golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= @@ -2279,6 +2371,7 @@ golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjs golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -2646,6 +2739,7 @@ gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= diff --git a/pkg/config/envs.go b/pkg/config/envs.go index 99a67edc6..c4fe54ae4 100644 --- a/pkg/config/envs.go +++ b/pkg/config/envs.go @@ -22,5 +22,6 @@ var ( GatewayName = env.RegisterStringVar("GATEWAY_NAME", "higress-gateway", "").Get() // Revision is the value of the Istio control plane revision, e.g. "canary", // and is the value used by the "istio.io/rev" label. - Revision = env.Register("REVISION", "", "").Get() + Revision = env.Register("REVISION", "", "").Get() + McpServerWasmImageUrl = env.RegisterStringVar("MCP_SERVER_WASM_IMAGE_URL", "oci://higress-registry.cn-hangzhou.cr.aliyuncs.com/mcp-server/all-in-one:1.0.0", "").Get() ) diff --git a/pkg/ingress/config/ingress_config.go b/pkg/ingress/config/ingress_config.go index c17c3babe..e63143197 100644 --- a/pkg/ingress/config/ingress_config.go +++ b/pkg/ingress/config/ingress_config.go @@ -590,6 +590,13 @@ func (m *IngressConfig) convertVirtualService(configs []common.WrapperConfig) [] Spec: vs, }) } + // add vs from naco3 for mcp server + if m.RegistryReconciler != nil { + allConfigsFromMcp := m.RegistryReconciler.GetAllConfigs(gvk.VirtualService) + for _, cfg := range allConfigsFromMcp { + out = append(out, *cfg) + } + } // We generate some specific envoy filter here to avoid duplicated computation. m.convertEnvoyFilter(&convertOptions) @@ -676,6 +683,13 @@ func (m *IngressConfig) convertWasmPlugin([]common.WrapperConfig) []config.Confi Spec: wasmPlugin, }) } + // add wasm plugin from nacos for mcp server + if m.RegistryReconciler != nil { + wasmFromMcp := m.RegistryReconciler.GetAllConfigs(gvk.WasmPlugin) + for _, cfg := range wasmFromMcp { + out = append(out, *cfg) + } + } return out } @@ -686,6 +700,7 @@ func (m *IngressConfig) convertServiceEntry([]common.WrapperConfig) []config.Con serviceEntries := m.RegistryReconciler.GetAllServiceWrapper() IngressLog.Infof("Found mcp serviceEntries %v", serviceEntries) out := make([]config.Config, 0, len(serviceEntries)) + hostSets := sets.Set[string]{} for _, se := range serviceEntries { out = append(out, config.Config{ Meta: config.Meta{ @@ -700,6 +715,15 @@ func (m *IngressConfig) convertServiceEntry([]common.WrapperConfig) []config.Con }, Spec: se.ServiceEntry, }) + hostSets.Insert(se.ServiceEntry.Hosts[0]) + } + // add service entry by host from nacos3 for mcp server + seFromMcp := m.RegistryReconciler.GetAllConfigs(gvk.ServiceEntry) + for _, cfg := range seFromMcp { + se := cfg.Spec.(*networking.ServiceEntry) + if !hostSets.Contains(se.Hosts[0]) { + out = append(out, *cfg) + } } return out } @@ -770,6 +794,10 @@ func (m *IngressConfig) convertDestinationRule(configs []common.WrapperConfig) [ if !exist { destinationRules[serviceName] = destinationRuleWrapper } else if dr.DestinationRule.TrafficPolicy != nil { + if dr.DestinationRule.TrafficPolicy.LoadBalancer == nil && + destinationRuleWrapper.DestinationRule.TrafficPolicy.LoadBalancer != nil { + dr.DestinationRule.TrafficPolicy.LoadBalancer = destinationRuleWrapper.DestinationRule.TrafficPolicy.LoadBalancer + } portTrafficPolicy := destinationRuleWrapper.DestinationRule.TrafficPolicy.PortLevelSettings[0] portUpdated := false for _, policy := range dr.DestinationRule.TrafficPolicy.PortLevelSettings { @@ -1137,6 +1165,28 @@ func (m *IngressConfig) AddOrUpdateMcpBridge(clusterNamespacedName util.ClusterN // Set this label so that we do not compare configs and just push. Labels: map[string]string{constants.AlwaysPushLabel: "true"}, } + vsMetadata := config.Meta{ + Name: "mcpbridge-virtualservice", + Namespace: m.namespace, + GroupVersionKind: gvk.VirtualService, + // Set this label so that we do not compare configs and just push. + Labels: map[string]string{constants.AlwaysPushLabel: "true"}, + } + wasmMetadata := config.Meta{ + Name: "mcpbridge-wasmplugin", + Namespace: m.namespace, + GroupVersionKind: gvk.WasmPlugin, + // Set this label so that we do not compare configs and just push. + Labels: map[string]string{constants.AlwaysPushLabel: "true"}, + } + efMetadata := config.Meta{ + Name: "mcpbridge-envoyfilter", + Namespace: m.namespace, + GroupVersionKind: gvk.EnvoyFilter, + // Set this label so that we do not compare configs and just push. + Labels: map[string]string{constants.AlwaysPushLabel: "true"}, + } + for _, f := range m.serviceEntryHandlers { IngressLog.Debug("McpBridge triggerd serviceEntry update") f(config.Config{Meta: seMetadata}, config.Config{Meta: seMetadata}, istiomodel.EventUpdate) @@ -1145,9 +1195,22 @@ func (m *IngressConfig) AddOrUpdateMcpBridge(clusterNamespacedName util.ClusterN IngressLog.Debug("McpBridge triggerd destinationRule update") f(config.Config{Meta: drMetadata}, config.Config{Meta: drMetadata}, istiomodel.EventUpdate) } - }, m.localKubeClient, m.namespace) + for _, f := range m.virtualServiceHandlers { + IngressLog.Debug("McpBridge triggerd virtualservice update") + f(config.Config{Meta: vsMetadata}, config.Config{Meta: vsMetadata}, istiomodel.EventUpdate) + } + for _, f := range m.wasmPluginHandlers { + IngressLog.Debug("McpBridge triggerd wasmplugin update") + f(config.Config{Meta: wasmMetadata}, config.Config{Meta: wasmMetadata}, istiomodel.EventUpdate) + } + for _, f := range m.envoyFilterHandlers { + IngressLog.Debug("McpBridge triggerd envoyfilter update") + f(config.Config{Meta: efMetadata}, config.Config{Meta: efMetadata}, istiomodel.EventUpdate) + } + }, m.localKubeClient, m.namespace, m.clusterId.String()) } reconciler := m.RegistryReconciler + m.configmapMgr.SetMcpReconciler(m.RegistryReconciler) err = reconciler.Reconcile(mcpbridge) if err != nil { IngressLog.Errorf("Mcpbridge reconcile failed, err:%v", err) diff --git a/pkg/ingress/kube/configmap/controller.go b/pkg/ingress/kube/configmap/controller.go index a46ac4c64..d48cfd26a 100644 --- a/pkg/ingress/kube/configmap/controller.go +++ b/pkg/ingress/kube/configmap/controller.go @@ -18,6 +18,7 @@ import ( "reflect" "sync/atomic" + "github.com/alibaba/higress/registry/reconcile" "istio.io/istio/pilot/pkg/model" "istio.io/istio/pkg/cluster" "istio.io/istio/pkg/config" @@ -58,6 +59,7 @@ type ItemController interface { ValidHigressConfig(higressConfig *HigressConfig) error ConstructEnvoyFilters() ([]*config.Config, error) RegisterItemEventHandler(eventHandler ItemEventHandler) + RegisterMcpReconciler(reconciler *reconcile.Reconciler) } type ConfigmapMgr struct { @@ -111,6 +113,12 @@ func (c *ConfigmapMgr) GetHigressConfig() *HigressConfig { return nil } +func (c *ConfigmapMgr) SetMcpReconciler(reconciler *reconcile.Reconciler) { + for _, itemController := range c.ItemControllers { + itemController.RegisterMcpReconciler(reconciler) + } +} + func (c *ConfigmapMgr) AddItemControllers(controllers ...ItemController) { c.ItemControllers = append(c.ItemControllers, controllers...) } diff --git a/pkg/ingress/kube/configmap/global.go b/pkg/ingress/kube/configmap/global.go index 0804062d3..f1fdcaa05 100644 --- a/pkg/ingress/kube/configmap/global.go +++ b/pkg/ingress/kube/configmap/global.go @@ -21,6 +21,7 @@ import ( "github.com/alibaba/higress/pkg/ingress/kube/util" . "github.com/alibaba/higress/pkg/ingress/log" + "github.com/alibaba/higress/registry/reconcile" networking "istio.io/api/networking/v1alpha3" "istio.io/istio/pkg/config" "istio.io/istio/pkg/config/schema/gvk" @@ -376,6 +377,9 @@ func (g *GlobalOptionController) RegisterItemEventHandler(eventHandler ItemEvent g.eventHandler = eventHandler } +func (g *GlobalOptionController) RegisterMcpReconciler(reconciler *reconcile.Reconciler) { +} + // generateDownstreamEnvoyFilter generates the downstream envoy filter. func (g *GlobalOptionController) generateDownstreamEnvoyFilter(downstreamValueStruct string, bufferLimitStruct string, routeTimeoutStruct string, namespace string) []*networking.EnvoyFilter_EnvoyConfigObjectPatch { var downstreamConfig []*networking.EnvoyFilter_EnvoyConfigObjectPatch diff --git a/pkg/ingress/kube/configmap/gzip.go b/pkg/ingress/kube/configmap/gzip.go index 766b418e9..216f9fc3f 100644 --- a/pkg/ingress/kube/configmap/gzip.go +++ b/pkg/ingress/kube/configmap/gzip.go @@ -23,6 +23,7 @@ import ( "github.com/alibaba/higress/pkg/ingress/kube/util" . "github.com/alibaba/higress/pkg/ingress/log" + "github.com/alibaba/higress/registry/reconcile" networking "istio.io/api/networking/v1alpha3" "istio.io/istio/pkg/config" "istio.io/istio/pkg/config/schema/gvk" @@ -291,6 +292,9 @@ func (g *GzipController) RegisterItemEventHandler(eventHandler ItemEventHandler) g.eventHandler = eventHandler } +func (g *GzipController) RegisterMcpReconciler(reconciler *reconcile.Reconciler) { +} + func (g *GzipController) constructGzipStruct(gzip *Gzip, namespace string) string { gzipConfig := "" contentType := "" diff --git a/pkg/ingress/kube/configmap/mcp_server.go b/pkg/ingress/kube/configmap/mcp_server.go index e83875c59..dc77efc2b 100644 --- a/pkg/ingress/kube/configmap/mcp_server.go +++ b/pkg/ingress/kube/configmap/mcp_server.go @@ -24,6 +24,7 @@ import ( "github.com/alibaba/higress/pkg/ingress/kube/util" . "github.com/alibaba/higress/pkg/ingress/log" + "github.com/alibaba/higress/registry/reconcile" networking "istio.io/api/networking/v1alpha3" "istio.io/istio/pkg/config" "istio.io/istio/pkg/config/schema/gvk" @@ -215,6 +216,7 @@ type McpServerController struct { mcpServer atomic.Value Name string eventHandler ItemEventHandler + reconclier *reconcile.Reconciler } func NewMcpServerController(namespace string) *McpServerController { @@ -288,6 +290,10 @@ func (m *McpServerController) RegisterItemEventHandler(eventHandler ItemEventHan m.eventHandler = eventHandler } +func (m *McpServerController) RegisterMcpReconciler(reconciler *reconcile.Reconciler) { + m.reconclier = reconciler +} + func (m *McpServerController) ConstructEnvoyFilters() ([]*config.Config, error) { configs := make([]*config.Config, 0) mcpServer := m.GetMcpServer() @@ -381,18 +387,37 @@ func (m *McpServerController) ConstructEnvoyFilters() ([]*config.Config, error) func (m *McpServerController) constructMcpSessionStruct(mcp *McpServer) string { // Build match_list configuration matchList := "[]" + var matchConfigs []string if len(mcp.MatchList) > 0 { - matchConfigs := make([]string, len(mcp.MatchList)) - for i, rule := range mcp.MatchList { - matchConfigs[i] = fmt.Sprintf(`{ + for _, rule := range mcp.MatchList { + matchConfigs = append(matchConfigs, fmt.Sprintf(`{ "match_rule_domain": "%s", "match_rule_path": "%s", "match_rule_type": "%s" - }`, rule.MatchRuleDomain, rule.MatchRulePath, rule.MatchRuleType) + }`, rule.MatchRuleDomain, rule.MatchRulePath, rule.MatchRuleType)) } - matchList = fmt.Sprintf("[%s]", strings.Join(matchConfigs, ",")) } + if m.reconclier != nil { + vsFromMcp := m.reconclier.GetAllConfigs(gvk.VirtualService) + for _, c := range vsFromMcp { + vs := c.Spec.(*networking.VirtualService) + var host string + if len(vs.Hosts) > 1 { + host = fmt.Sprintf("(%s)", strings.Join(vs.Hosts, "|")) + } else { + host = vs.Hosts[0] + } + path := vs.Http[0].Match[0].Uri.GetPrefix() + matchConfigs = append(matchConfigs, fmt.Sprintf(`{ + "match_rule_domain": "%s", + "match_rule_path": "%s", + "match_rule_type": "prefix" + }`, host, path)) + } + } + matchList = fmt.Sprintf("[%s]", strings.Join(matchConfigs, ",")) + // Build redis configuration redisConfig := "null" if mcp.Redis != nil { diff --git a/pkg/ingress/kube/configmap/tracing.go b/pkg/ingress/kube/configmap/tracing.go index 0529ccf85..b3c0fe422 100644 --- a/pkg/ingress/kube/configmap/tracing.go +++ b/pkg/ingress/kube/configmap/tracing.go @@ -21,6 +21,7 @@ import ( "reflect" "sync/atomic" + "github.com/alibaba/higress/registry/reconcile" "istio.io/istio/pkg/config" "istio.io/istio/pkg/config/schema/gvk" @@ -237,6 +238,9 @@ func (t *TracingController) RegisterItemEventHandler(eventHandler ItemEventHandl t.eventHandler = eventHandler } +func (t *TracingController) RegisterMcpReconciler(reconciler *reconcile.Reconciler) { +} + func (t *TracingController) ConstructEnvoyFilters() ([]*config.Config, error) { configs := make([]*config.Config, 0) tracing := t.GetTracing() diff --git a/registry/mcp_model.go b/registry/mcp_model.go new file mode 100644 index 000000000..7c0c32488 --- /dev/null +++ b/registry/mcp_model.go @@ -0,0 +1,155 @@ +// 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 registry + +const ( + JsonGoTemplateType = "json-go-template" + + IstioMcpAutoGeneratedPrefix = "istio-autogenerated-mcp" + IstioMcpAutoGeneratedVsName = IstioMcpAutoGeneratedPrefix + "-vs" + IstioMcpAutoGeneratedSeName = IstioMcpAutoGeneratedPrefix + "-se" + IstioMcpAutoGeneratedDrName = IstioMcpAutoGeneratedPrefix + "-dr" + IstioMcpAutoGeneratedHttpRouteName = IstioMcpAutoGeneratedPrefix + "-httproute" + + DefaultMcpToolsGroup = "mcp-tools" + DefaultMcpCredentialsGroup = "credentials" + DefaultNacosServiceNamespace = "public" + + StdioProtocol = "stdio" + HttpProtocol = "http" + DubboProtocol = "dubbo" + McpSSEProtocol = "mcp-sse" + McpStreambleProtocol = "mcp-streamble" +) + +type McpToolArgsType string + +// WasmPluginConfig Struct for mcp tool wasm plugin marshal +type WasmPluginConfig struct { + Rules []*McpServerRule `json:"_rules_"` +} + +type McpServerRule struct { + MatchRoute []string `json:"_match_route_,omitempty"` + Server *ServerConfig `json:"server"` + Tools []*McpTool `json:"tools"` +} + +type ServerConfig struct { + Name string `json:"name,omitempty"` + Config map[string]interface{} `json:"config,omitempty"` + AllowTools []string `json:"allowTools,omitempty"` +} + +type McpTool struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Args []*ToolArgs `json:"args,omitempty"` + RequestTemplate *RequestTemplate `json:"requestTemplate"` + ResponseTemplate *ResponseTemplate `json:"responseTemplate"` +} + +type ToolArgs struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Type string `json:"type,omitempty"` + Required bool `json:"required,omitempty"` + Default interface{} `json:"default,omitempty"` + Enum []interface{} `json:"enum,omitempty"` + Items []interface{} `json:"items,omitempty"` + Properties interface{} `json:"properties,omitempty"` + Position string `json:"position,omitempty"` +} + +type RequestTemplate struct { + URL string `json:"url"` + Method string `json:"method"` + Headers []*RequestTemplateHeaders `json:"headers,omitempty"` + Body string `json:"body,omitempty"` + ArgsToJsonBody bool `json:"argsToJsonBody,omitempty"` + ArgsToUrlParam bool `json:"argsToUrlParam,omitempty"` + ArgsToFormBody bool `json:"argsToFormBody,omitempty"` +} + +type RequestTemplateHeaders struct { + Key string `json:"key"` + Value string `json:"value"` +} + +type ResponseTemplate struct { + Body string `json:"body,omitempty"` + PrependBody string `json:"prependBody,omitempty"` + AppendBody string `json:"appendBody,omitempty"` +} + +// McpServer Struct for mcp server json unmarshal +type McpServer struct { + Protocol string `json:"protocol,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Version string `json:"version,omitempty"` + Enabled bool `json:"enabled,omitempty"` + RemoteServerConfig *RemoteServerConfig `json:"remoteServerConfig,omitempty"` + Credentials map[string]*CredentialRef `json:"credentials,omitempty"` + ToolsDescriptionRef string `json:"toolsDescriptionRef,omitempty"` + PromptDescriptionRef string `json:"promptDescriptionRef,omitempty"` + ResourceDescriptionRef string `json:"resourceDescriptionRef,omitempty"` +} + +type RemoteServerConfig struct { + ServiceRef *ServiceRef `json:"serviceRef,omitempty"` + ExportPath string `json:"exportPath,omitempty"` + BackendProtocol string `json:"backendProtocol,omitempty"` +} + +type CredentialRef struct { + Ref string `json:"ref,omitempty"` +} + +type ServiceRef struct { + NamespaceId string `json:"namespaceId,omitempty"` + GroupName string `json:"groupName,omitempty"` + ServiceName string `json:"serviceName,omitempty"` +} + +// McpToolConfig Struct for mcp tool json unmarshal +type McpToolConfig struct { + Tools []*ToolDescription `json:"tools,omitempty"` + ToolsMeta map[string]*ToolsMeta `json:"toolsMeta,omitempty"` +} + +type ToolDescription struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + InputSchema InputSchema `json:"inputSchema"` +} + +type InputSchema struct { + Type string `json:"type,omitempty"` + Properties map[string]interface{} `json:"properties,omitempty"` + Required []string `json:"required,omitempty"` +} + +type ToolsMeta struct { + InvokeContext map[string]string `json:"InvokeContext,omitempty"` + Enabled bool `json:"Enabled,omitempty"` + Templates map[string]interface{} `json:"Templates,omitempty"` +} + +type JsonGoTemplate struct { + RequestTemplate RequestTemplate `json:"requestTemplate"` + ResponseTemplate ResponseTemplate `json:"responseTemplate"` + ArgsPosition map[string]string `json:"argsPosition,omitempty"` +} diff --git a/registry/memory/cache.go b/registry/memory/cache.go index 9e72a9977..75faf897a 100644 --- a/registry/memory/cache.go +++ b/registry/memory/cache.go @@ -15,12 +15,21 @@ package memory import ( + "encoding/json" "sort" "strconv" "sync" "time" + higressconfig "github.com/alibaba/higress/pkg/config" + "github.com/alibaba/higress/registry" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/wrapperspb" + extensions "istio.io/api/extensions/v1alpha1" "istio.io/api/networking/v1alpha3" + "istio.io/istio/pkg/config" + "istio.io/istio/pkg/config/schema/gvk" "istio.io/pkg/log" "github.com/alibaba/higress/pkg/common" @@ -30,6 +39,8 @@ import ( type Cache interface { UpdateServiceWrapper(service string, data *ServiceWrapper) DeleteServiceWrapper(service string) + UpdateConfigCache(kind config.GroupVersionKind, key string, config *config.Config, forceDelete bool) + GetAllConfigs(kind config.GroupVersionKind) map[string]*config.Config PurgeStaleService() UpdateServiceEntryEndpointWrapper(service, ip, regionId, zoneId, protocol string, labels map[string]string) GetServiceByEndpoints(requestVersions, endpoints map[string]bool, versionKey string, protocol common.Protocol) map[string][]string @@ -44,6 +55,7 @@ func NewCache() Cache { return &store{ mux: &sync.RWMutex{}, sew: make(map[string]*ServiceWrapper), + configs: make(map[string]map[string]*config.Config), toBeUpdated: make([]*ServiceWrapper, 0), toBeDeleted: make([]*ServiceWrapper, 0), ip2services: make(map[string]map[string]bool), @@ -54,12 +66,85 @@ func NewCache() Cache { type store struct { mux *sync.RWMutex sew map[string]*ServiceWrapper + configs map[string]map[string]*config.Config toBeUpdated []*ServiceWrapper toBeDeleted []*ServiceWrapper ip2services map[string]map[string]bool deferedDelete map[string]struct{} } +func (s *store) GetAllConfigs(kind config.GroupVersionKind) map[string]*config.Config { + s.mux.Lock() + defer s.mux.Unlock() + cfgs, exist := s.configs[kind.String()] + if !exist { + return map[string]*config.Config{} + } + if kind == gvk.WasmPlugin { + pluginConfig := ®istry.WasmPluginConfig{} + var ns string + for _, cfg := range cfgs { + ns = cfg.Namespace + rule := cfg.Spec.(*registry.McpServerRule) + pluginConfig.Rules = append(pluginConfig.Rules, rule) + } + rulesBytes, err := json.Marshal(pluginConfig) + if err != nil { + log.Errorf("marshal mcp wasm plugin config error %v", err) + return map[string]*config.Config{} + } + pbs := &structpb.Struct{} + if err = protojson.Unmarshal(rulesBytes, pbs); err != nil { + log.Errorf("unmarshal mcp wasm plugin config error %v", err) + return map[string]*config.Config{} + } + wasmPlugin := &extensions.WasmPlugin{ + ImagePullPolicy: extensions.PullPolicy_Always, + Phase: extensions.PluginPhase_UNSPECIFIED_PHASE, + Priority: &wrapperspb.Int32Value{Value: 30}, + PluginConfig: pbs, + Url: higressconfig.McpServerWasmImageUrl, + } + + return map[string]*config.Config{"wasm": &config.Config{ + Meta: config.Meta{ + GroupVersionKind: gvk.WasmPlugin, + Name: "istio-autogenerated-mcp-wasmplugin", + Namespace: ns, + }, + Spec: wasmPlugin, + }} + } + + return cfgs +} + +func (s *store) UpdateConfigCache(kind config.GroupVersionKind, key string, cfg *config.Config, forceDelete bool) { + if cfg == nil && !forceDelete { + return + } + + s.mux.Lock() + if forceDelete { + for _, allConfigs := range s.configs { + delete(allConfigs, key) + } + log.Infof("Delete config %s in cache", key) + } else { + if _, exist := s.configs[kind.String()]; !exist { + s.configs[kind.String()] = make(map[string]*config.Config) + } + + if _, exist := s.configs[kind.String()][key]; exist { + log.Infof("Update kind %s config %s", kind.String(), key) + } else { + log.Infof("Add kind %s config %s", kind.String(), key) + } + s.configs[kind.String()][key] = cfg + } + s.mux.Unlock() +} + func (s *store) UpdateServiceEntryEndpointWrapper(service, ip, regionId, zoneId, protocol string, labels map[string]string) { s.mux.Lock() defer s.mux.Unlock() @@ -226,6 +311,15 @@ func (s *store) GetAllDestinationRuleWrapper() []*ingress.WrapperDestinationRule drwList = append(drwList, serviceEntryWrapper.DeepCopy().DestinationRuleWrapper) } } + configFromMcp := s.configs[gvk.DestinationRule.String()] + for _, cfg := range configFromMcp { + dr := cfg.Spec.(*v1alpha3.DestinationRule) + drwList = append(drwList, &ingress.WrapperDestinationRule{ + DestinationRule: dr, + ServiceKey: ingress.ServiceKey{ServiceFQDN: dr.Host}, + }) + } + return drwList } diff --git a/registry/nacos/mcpserver/util.go b/registry/nacos/mcpserver/util.go new file mode 100644 index 000000000..fd5496a5d --- /dev/null +++ b/registry/nacos/mcpserver/util.go @@ -0,0 +1,180 @@ +// 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 mcpserver + +import ( + "fmt" + + "github.com/nacos-group/nacos-sdk-go/v2/clients/config_client" + "github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client" + "github.com/nacos-group/nacos-sdk-go/v2/model" + "github.com/nacos-group/nacos-sdk-go/v2/vo" +) + +type MultiConfigListener struct { + configClient config_client.IConfigClient + onChange func(map[string]string) + configCache map[string]string + innerCallback func(string, string, string, string) +} + +func NewMultiConfigListener(configClient config_client.IConfigClient, onChange func(map[string]string)) *MultiConfigListener { + result := &MultiConfigListener{ + configClient: configClient, + configCache: make(map[string]string), + onChange: onChange, + } + + result.innerCallback = func(namespace string, group string, dataId string, content string) { + result.configCache[group+DefaultJoiner+dataId] = content + result.onChange(result.configCache) + } + + return result +} + +func (l *MultiConfigListener) StartListen(configs []vo.ConfigParam) error { + for _, config := range configs { + content, err := l.configClient.GetConfig(vo.ConfigParam{ + DataId: config.DataId, + Group: config.Group, + }) + + if err != nil { + return fmt.Errorf("get config %s/%s err: %v", config.Group, config.DataId, err) + } + l.configCache[config.Group+DefaultJoiner+config.DataId] = content + err = l.configClient.ListenConfig(vo.ConfigParam{ + DataId: config.DataId, + Group: config.Group, + OnChange: l.innerCallback, + }) + + if err != nil { + return fmt.Errorf("listener to config %s/%s error: %w", config.Group, config.DataId, err) + } + } + + l.onChange(l.configCache) + return nil +} + +func (l *MultiConfigListener) Stop() { + l.configClient.CloseClient() +} + +func (l *MultiConfigListener) CancelListen(configs []vo.ConfigParam) error { + for _, config := range configs { + if _, ok := l.configCache[config.Group+DefaultJoiner+config.DataId]; ok { + err := l.configClient.CancelListenConfig(vo.ConfigParam{ + DataId: config.DataId, + Group: config.Group, + }) + + if err != nil { + return fmt.Errorf("cancel config %s/%s error: %w", config.Group, config.DataId, err) + } + delete(l.configCache, config.Group+config.DataId) + } + } + return nil +} + +type ServiceCache struct { + services map[string]*NacosServiceRef + client naming_client.INamingClient +} + +type NacosServiceRef struct { + refs map[string]func([]model.Instance) + callback func(services []model.Instance, err error) + instances *[]model.Instance +} + +func NewServiceCache(client naming_client.INamingClient) *ServiceCache { + return &ServiceCache{ + client: client, + services: make(map[string]*NacosServiceRef), + } +} + +func (c *ServiceCache) AddListener(group string, serviceName string, key string, callback func([]model.Instance)) error { + uniqueServiceName := c.makeServiceUniqueName(group, serviceName) + if _, ok := c.services[uniqueServiceName]; !ok { + instances, err := c.client.SelectAllInstances(vo.SelectAllInstancesParam{ + GroupName: group, + ServiceName: serviceName, + }) + + if err != nil { + return err + } + + ref := &NacosServiceRef{ + refs: map[string]func([]model.Instance){}, + instances: &instances, + } + + ref.callback = func(services []model.Instance, err error) { + ref.instances = &services + for _, refCallback := range ref.refs { + refCallback(*ref.instances) + } + } + + c.services[uniqueServiceName] = ref + + err = c.client.Subscribe(&vo.SubscribeParam{ + GroupName: group, + ServiceName: serviceName, + SubscribeCallback: ref.callback, + }) + if err != nil { + return err + } + } + + ref := c.services[uniqueServiceName] + ref.refs[key] = callback + callback(*ref.instances) + return nil +} + +func (c *ServiceCache) RemoveListener(group string, serviceName string, key string) error { + if ref, ok := c.services[c.makeServiceUniqueName(group, serviceName)]; ok { + delete(ref.refs, key) + if len(ref.refs) == 0 { + err := c.client.Unsubscribe(&vo.SubscribeParam{ + GroupName: group, + ServiceName: serviceName, + SubscribeCallback: ref.callback, + }) + + delete(c.services, c.makeServiceUniqueName(group, serviceName)) + if err != nil { + return err + } + } + } + return nil +} + +func (c *ServiceCache) makeServiceUniqueName(group string, serviceName string) string { + return fmt.Sprintf("%s-%s", group, serviceName) +} + +func (c *ServiceCache) Stop() { + c.client.CloseClient() +} diff --git a/registry/nacos/mcpserver/watcher.go b/registry/nacos/mcpserver/watcher.go new file mode 100644 index 000000000..fcd2105b1 --- /dev/null +++ b/registry/nacos/mcpserver/watcher.go @@ -0,0 +1,989 @@ +// 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 mcpserver + +import ( + "encoding/json" + "errors" + "fmt" + "net" + "strconv" + "strings" + "sync" + "time" + + apiv1 "github.com/alibaba/higress/api/networking/v1" + "github.com/alibaba/higress/pkg/common" + common2 "github.com/alibaba/higress/pkg/ingress/kube/common" + provider "github.com/alibaba/higress/registry" + "github.com/alibaba/higress/registry/memory" + "github.com/golang/protobuf/ptypes/wrappers" + "github.com/nacos-group/nacos-sdk-go/v2/clients" + "github.com/nacos-group/nacos-sdk-go/v2/clients/config_client" + "github.com/nacos-group/nacos-sdk-go/v2/common/constant" + "github.com/nacos-group/nacos-sdk-go/v2/model" + "github.com/nacos-group/nacos-sdk-go/v2/vo" + "go.uber.org/atomic" + "istio.io/api/networking/v1alpha3" + "istio.io/istio/pkg/config" + "istio.io/istio/pkg/config/constants" + "istio.io/istio/pkg/config/schema/gvk" + "istio.io/istio/pkg/log" + "istio.io/istio/pkg/util/sets" +) + +const ( + DefaultInitTimeout = time.Second * 10 + DefaultNacosTimeout = 5000 + DefaultNacosLogLevel = "info" + DefaultNacosLogDir = "/var/log/nacos/log/mcp/log" + DefaultNacosCacheDir = "/var/log/nacos/log/mcp/cache" + DefaultNacosNotLoadCache = true + DefaultNacosLogMaxAge = 3 + DefaultRefreshInterval = time.Second * 30 + DefaultRefreshIntervalLimit = time.Second * 10 + DefaultFetchPageSize = 50 + DefaultJoiner = "@@" + NacosV3LabelKey = "isV3" +) + +var mcpServerLog = log.RegisterScope("McpServer", "Nacos Mcp Server Watcher process.") + +type watcher struct { + provider.BaseWatcher + apiv1.RegistryConfig + watchingConfig map[string]bool + watchingConfigRefs map[string]sets.Set[string] + configToConfigListener map[string]*MultiConfigListener + serviceCache map[string]*ServiceCache + configToService map[string]string + credentialKeyToName map[string]map[string]string + RegistryType provider.ServiceRegistryType + Status provider.WatcherStatus + configClient config_client.IConfigClient + serverConfig []constant.ServerConfig + cache memory.Cache + mutex *sync.Mutex + subMutex *sync.Mutex + callbackMutex *sync.Mutex + stop chan struct{} + isStop bool + updateCacheWhenEmpty bool + nacosClientConfig *constant.ClientConfig + namespace string + clusterId string +} + +type WatcherOption func(w *watcher) + +func NewWatcher(cache memory.Cache, opts ...WatcherOption) (provider.Watcher, error) { + w := &watcher{ + watchingConfig: make(map[string]bool), + configToService: make(map[string]string), + watchingConfigRefs: make(map[string]sets.Set[string]), + configToConfigListener: make(map[string]*MultiConfigListener), + credentialKeyToName: make(map[string]map[string]string), + serviceCache: map[string]*ServiceCache{}, + RegistryType: "nacos3", + Status: provider.UnHealthy, + cache: cache, + mutex: &sync.Mutex{}, + subMutex: &sync.Mutex{}, + callbackMutex: &sync.Mutex{}, + stop: make(chan struct{}), + } + + w.NacosRefreshInterval = int64(DefaultRefreshInterval) + + for _, opt := range opts { + opt(w) + } + + if w.NacosNamespace == "" { + w.NacosNamespace = w.NacosNamespaceId + } + + mcpServerLog.Infof("new nacos mcp server watcher with config Name:%s", w.Name) + + w.nacosClientConfig = constant.NewClientConfig( + constant.WithTimeoutMs(DefaultNacosTimeout), + constant.WithLogLevel(DefaultNacosLogLevel), + constant.WithLogDir(DefaultNacosLogDir), + constant.WithCacheDir(DefaultNacosCacheDir), + constant.WithNotLoadCacheAtStart(DefaultNacosNotLoadCache), + constant.WithLogRollingConfig(&constant.ClientLogRollingConfig{ + MaxAge: DefaultNacosLogMaxAge, + }), + constant.WithUpdateCacheWhenEmpty(w.updateCacheWhenEmpty), + constant.WithNamespaceId(w.NacosNamespaceId), + constant.WithAccessKey(w.NacosAccessKey), + constant.WithSecretKey(w.NacosSecretKey), + ) + + initTimer := time.NewTimer(DefaultInitTimeout) + w.serverConfig = []constant.ServerConfig{ + *constant.NewServerConfig(w.Domain, uint64(w.Port)), + } + + success := make(chan struct{}) + go func() { + configClient, err := clients.NewConfigClient(vo.NacosClientParam{ + ClientConfig: w.nacosClientConfig, + ServerConfigs: w.serverConfig, + }) + if err == nil { + w.configClient = configClient + close(success) + } else { + mcpServerLog.Errorf("can not create naming client, err:%v", err) + } + }() + + select { + case <-initTimer.C: + return nil, errors.New("new nacos mcp server watcher timeout") + case <-success: + return w, nil + } +} + +func WithNacosAddressServer(nacosAddressServer string) WatcherOption { + return func(w *watcher) { + w.NacosAddressServer = nacosAddressServer + } +} + +func WithNacosAccessKey(nacosAccessKey string) WatcherOption { + return func(w *watcher) { + w.NacosAccessKey = nacosAccessKey + } +} + +func WithNacosSecretKey(nacosSecretKey string) WatcherOption { + return func(w *watcher) { + w.NacosSecretKey = nacosSecretKey + } +} + +func WithNacosNamespaceId(nacosNamespaceId string) WatcherOption { + return func(w *watcher) { + if nacosNamespaceId == "" { + w.NacosNamespaceId = "nacos-default-mcp" + } else { + w.NacosNamespaceId = nacosNamespaceId + } + } +} + +func WithNacosNamespace(nacosNamespace string) WatcherOption { + return func(w *watcher) { + w.NacosNamespace = nacosNamespace + } +} + +func WithNacosGroups(nacosGroups []string) WatcherOption { + return func(w *watcher) { + if len(nacosGroups) == 0 { + w.NacosGroups = []string{"mcp-server"} + } else { + w.NacosGroups = nacosGroups + } + } +} + +func WithNacosRefreshInterval(refreshInterval int64) WatcherOption { + return func(w *watcher) { + if refreshInterval < int64(DefaultRefreshIntervalLimit) { + refreshInterval = int64(DefaultRefreshIntervalLimit) + } + w.NacosRefreshInterval = refreshInterval + } +} + +func WithType(t string) WatcherOption { + return func(w *watcher) { + w.Type = t + } +} + +func WithName(name string) WatcherOption { + return func(w *watcher) { + w.Name = name + } +} + +func WithDomain(domain string) WatcherOption { + return func(w *watcher) { + w.Domain = domain + } +} + +func WithPort(port uint32) WatcherOption { + return func(w *watcher) { + w.Port = port + } +} + +func WithMcpExportDomains(exportDomains []string) WatcherOption { + return func(w *watcher) { + w.McpServerExportDomains = exportDomains + } +} + +func WithMcpBaseUrl(url string) WatcherOption { + return func(w *watcher) { + w.McpServerBaseUrl = url + } +} + +func WithEnableMcpServer(enable *wrappers.BoolValue) WatcherOption { + return func(w *watcher) { + w.EnableMCPServer = enable + } +} + +func WithNamespace(ns string) WatcherOption { + return func(w *watcher) { + w.namespace = ns + } +} + +func WithClusterId(id string) WatcherOption { + return func(w *watcher) { + w.clusterId = id + } +} + +func (w *watcher) Run() { + ticker := time.NewTicker(time.Duration(w.NacosRefreshInterval)) + defer ticker.Stop() + w.Status = provider.ProbeWatcherStatus(w.Domain, strconv.FormatUint(uint64(w.Port), 10)) + err := w.fetchAllMcpConfig() + if err != nil { + mcpServerLog.Errorf("first fetch mcp server config failed, err:%v", err) + } else { + w.Ready(true) + } + for { + select { + case <-ticker.C: + err := w.fetchAllMcpConfig() + if err != nil { + mcpServerLog.Errorf("fetch mcp server config failed, err:%v", err) + } else { + w.Ready(true) + } + case <-w.stop: + return + } + } +} + +func (w *watcher) fetchAllMcpConfig() error { + w.mutex.Lock() + defer w.mutex.Unlock() + + if w.isStop { + return nil + } + fetchedConfigs := make(map[string]bool) + var tries int + isV3 := true + if w.EnableMCPServer != nil { + isV3 = w.EnableMCPServer.GetValue() + } + for _, groupName := range w.NacosGroups { + for page := 1; ; page++ { + ss, err := w.configClient.SearchConfig(vo.SearchConfigParam{ + Group: groupName, + Search: "blur", + PageNo: page, + PageSize: DefaultFetchPageSize, + IsV3: isV3, + }) + if err != nil { + if tries > 10 { + return err + } + mcpServerLog.Errorf("fetch nacos config list failed, err:%v, pageNo:%d", err, page) + page-- + tries++ + continue + } + for _, item := range ss.PageItems { + fetchedConfigs[groupName+DefaultJoiner+item.DataId] = true + } + if len(ss.PageItems) < DefaultFetchPageSize { + break + } + } + } + + for key := range w.watchingConfig { + if _, exist := fetchedConfigs[key]; !exist { + s := strings.Split(key, DefaultJoiner) + err := w.unsubscribe(s[0], s[1]) + if err != nil { + return err + } + delete(w.watchingConfig, key) + } + } + + wg := sync.WaitGroup{} + subscribeFailed := atomic.NewBool(false) + watchingKeys := make(chan string, len(fetchedConfigs)) + for key := range fetchedConfigs { + s := strings.Split(key, DefaultJoiner) + if _, exist := w.watchingConfig[key]; !exist { + wg.Add(1) + go func(k string) { + err := w.subscribe(s[0], s[1]) + if err != nil { + subscribeFailed.Store(true) + mcpServerLog.Errorf("subscribe failed, group: %v, service: %v, errors: %v", s[0], s[1], err) + } else { + watchingKeys <- k + } + wg.Done() + }(key) + } + } + wg.Wait() + close(watchingKeys) + for key := range watchingKeys { + w.watchingConfig[key] = true + } + if subscribeFailed.Load() { + return errors.New("subscribe services failed") + } + return nil +} + +func (w *watcher) unsubscribe(groupName string, dataId string) error { + mcpServerLog.Infof("unsubscribe mcp server, groupName:%s, dataId:%s", groupName, dataId) + defer w.UpdateService() + + err := w.configClient.CancelListenConfig(vo.ConfigParam{ + DataId: dataId, + Group: groupName, + }) + if err != nil { + mcpServerLog.Errorf("unsubscribe mcp server error:%v, groupName:%s, dataId:%s", err, groupName, dataId) + return err + } + key := strings.Join([]string{w.Name, w.NacosNamespace, groupName, dataId}, DefaultJoiner) + w.configToConfigListener[key].Stop() + delete(w.watchingConfigRefs, key) + delete(w.configToConfigListener, key) + // remove service for this config + configKey := strings.Join([]string{groupName, dataId}, DefaultJoiner) + svcInfo := w.configToService[configKey] + split := strings.Split(svcInfo, DefaultJoiner) + svcNamespace := split[0] + svcGroup := split[1] + svcName := split[2] + if w.serviceCache[svcNamespace] != nil { + err = w.serviceCache[svcNamespace].RemoveListener(svcGroup, svcName, configKey) + if err != nil { + mcpServerLog.Errorf("remove service listener error:%v, groupName:%s, dataId:%s", err, groupName, dataId) + } + } + delete(w.configToService, configKey) + + w.cache.UpdateConfigCache(config.GroupVersionKind{}, key, nil, true) + return nil +} + +func (w *watcher) subscribe(groupName string, dataId string) error { + mcpServerLog.Infof("subscribe mcp server, groupName:%s, dataId:%s", groupName, dataId) + // first we get this config and callback manually + content, err := w.configClient.GetConfig(vo.ConfigParam{ + DataId: dataId, + Group: groupName, + }) + if err != nil { + mcpServerLog.Errorf("get config %s/%s err: %v", groupName, dataId, err) + } else { + w.getConfigCallback(w.NacosNamespace, groupName, dataId, content) + } + // second, we set callback for this config + err = w.configClient.ListenConfig(vo.ConfigParam{ + DataId: dataId, + Group: groupName, + OnChange: w.getConfigCallback, + }) + if err != nil { + mcpServerLog.Errorf("subscribe mcp server error:%v, groupName:%s, dataId:%s", err, groupName, dataId) + return err + } + return nil +} + +func (w *watcher) getConfigCallback(namespace, group, dataId, data string) { + mcpServerLog.Infof("get config callback, namespace:%s, groupName:%s, dataId:%s", namespace, group, dataId) + + if data == "" { + return + } + + key := strings.Join([]string{w.Name, w.NacosNamespace, group, dataId}, DefaultJoiner) + routeName := fmt.Sprintf("%s-%s-%s", provider.IstioMcpAutoGeneratedHttpRouteName, group, strings.TrimSuffix(dataId, ".json")) + + mcpServer := &provider.McpServer{} + if err := json.Unmarshal([]byte(data), mcpServer); err != nil { + mcpServerLog.Errorf("Unmarshal config data to mcp server error:%v, namespace:%s, groupName:%s, dataId:%s", err, namespace, group, dataId) + return + } + if mcpServer.Protocol == provider.StdioProtocol || mcpServer.Protocol == provider.DubboProtocol || mcpServer.Protocol == provider.McpSSEProtocol { + return + } + // process mcp service + w.subMutex.Lock() + defer w.subMutex.Unlock() + if err := w.buildServiceEntryForMcpServer(mcpServer, group, dataId); err != nil { + mcpServerLog.Errorf("build service entry for mcp server failed, namespace %v, group: %v, dataId %v, errors: %v", namespace, group, dataId, err) + } + // process mcp wasm + // only generate wasm plugin for http protocol mcp server + if mcpServer.Protocol != provider.HttpProtocol { + return + } + if _, exist := w.configToConfigListener[key]; !exist { + w.configToConfigListener[key] = NewMultiConfigListener(w.configClient, w.multiCallback(mcpServer, routeName, key)) + } + if _, exist := w.watchingConfigRefs[key]; !exist { + w.watchingConfigRefs[key] = sets.New[string]() + } + listener := w.configToConfigListener[key] + + curRef := sets.Set[string]{} + // add description ref + curRef.Insert(strings.Join([]string{provider.DefaultMcpToolsGroup, mcpServer.ToolsDescriptionRef}, DefaultJoiner)) + // add credential ref + credentialNameMap := map[string]string{} + for name, ref := range mcpServer.Credentials { + credKey := strings.Join([]string{provider.DefaultMcpCredentialsGroup, ref.Ref}, DefaultJoiner) + curRef.Insert(credKey) + credentialNameMap[credKey] = name + } + w.callbackMutex.Lock() + w.credentialKeyToName[key] = credentialNameMap + w.callbackMutex.Unlock() + + toBeAdd := curRef.Difference(w.watchingConfigRefs[key]) + toBeDelete := w.watchingConfigRefs[key].Difference(curRef) + + var toBeListen, toBeUnListen []vo.ConfigParam + for item, _ := range toBeAdd { + split := strings.Split(item, DefaultJoiner) + toBeListen = append(toBeListen, vo.ConfigParam{ + Group: split[0], + DataId: split[1], + }) + } + for item, _ := range toBeDelete { + split := strings.Split(item, DefaultJoiner) + toBeUnListen = append(toBeUnListen, vo.ConfigParam{ + Group: split[0], + DataId: split[1], + }) + } + + // listen description and credential config + if len(toBeListen) > 0 { + if err := listener.StartListen(toBeListen); err != nil { + mcpServerLog.Errorf("listen config ref failed, group: %v, dataId %v, errors: %v", group, dataId, err) + } + } + // cancel listen description and credential config + if len(toBeUnListen) > 0 { + if err := listener.CancelListen(toBeUnListen); err != nil { + mcpServerLog.Errorf("cancel listen config ref failed, group: %v, dataId %v, errors: %v", group, dataId, err) + } + } +} + +func (w *watcher) multiCallback(server *provider.McpServer, routeName, configKey string) func(map[string]string) { + callback := func(configs map[string]string) { + defer w.UpdateService() + + mcpServerLog.Infof("callback, ref config changed: %s", configKey) + rule := &provider.McpServerRule{ + MatchRoute: []string{routeName}, + Server: &provider.ServerConfig{ + Name: server.Name, + Config: map[string]interface{}{}, + }, + } + + // process mcp credential + credentialConfig := map[string]interface{}{} + for key, data := range configs { + if strings.HasPrefix(key, provider.DefaultMcpToolsGroup) { + // skip mcp tool description + continue + } + var cred interface{} + if err := json.Unmarshal([]byte(data), &cred); err != nil { + mcpServerLog.Errorf("unmarshal credential data %v to map error:%v", key, err) + } + w.callbackMutex.Lock() + name := w.credentialKeyToName[configKey][key] + w.callbackMutex.Unlock() + credentialConfig[name] = cred + } + rule.Server.Config["credentials"] = credentialConfig + // process mcp tool description + var allowTools []string + for key, toolData := range configs { + if strings.HasPrefix(key, provider.DefaultMcpCredentialsGroup) { + // skip mcp credentials + continue + } + toolsDescription := &provider.McpToolConfig{} + if err := json.Unmarshal([]byte(toolData), toolsDescription); err != nil { + mcpServerLog.Errorf("unmarshal toolsDescriptionRef to mcp tool config error:%v", err) + } + for _, t := range toolsDescription.Tools { + convertTool := &provider.McpTool{Name: t.Name, Description: t.Description} + + toolMeta := toolsDescription.ToolsMeta[t.Name] + if toolMeta != nil && toolMeta.Enabled { + allowTools = append(allowTools, t.Name) + } + argsPosition, err := getArgsPositionFromToolMeta(toolMeta) + if err != nil { + mcpServerLog.Errorf("get args position from tool meta error:%v, tool name %v", err, t.Name) + } + + requiredMap := sets.Set[string]{} + for _, s := range t.InputSchema.Required { + requiredMap.Insert(s) + } + + for argsName, args := range t.InputSchema.Properties { + convertArgs, err := parseMcpArgs(args) + if err != nil { + mcpServerLog.Errorf("parse mcp args error:%v, tool name %v, args name %v", err, t.Name, argsName) + continue + } + convertArgs.Name = argsName + convertArgs.Required = requiredMap.Contains(argsName) + if pos, exist := argsPosition[argsName]; exist { + convertArgs.Position = pos + } + convertTool.Args = append(convertTool.Args, convertArgs) + mcpServerLog.Debugf("parseMcpArgs, toolArgs:%v", convertArgs) + } + + requestTemplate, err := getRequestTemplateFromToolMeta(toolMeta) + if err != nil { + mcpServerLog.Errorf("get request template from tool meta error:%v, tool name %v", err, t.Name) + } else { + convertTool.RequestTemplate = requestTemplate + } + + responseTemplate, err := getResponseTemplateFromToolMeta(toolMeta) + if err != nil { + mcpServerLog.Errorf("get response template from tool meta error:%v, tool name %v", err, t.Name) + } else { + convertTool.ResponseTemplate = responseTemplate + } + rule.Tools = append(rule.Tools, convertTool) + } + } + + rule.Server.AllowTools = allowTools + wasmPluginConfig := &config.Config{ + Meta: config.Meta{ + GroupVersionKind: gvk.WasmPlugin, + Namespace: w.namespace, + }, + Spec: rule, + } + w.cache.UpdateConfigCache(gvk.WasmPlugin, configKey, wasmPluginConfig, false) + } + return callback +} + +func (w *watcher) buildServiceEntryForMcpServer(mcpServer *provider.McpServer, configGroup, dataId string) error { + if mcpServer == nil || mcpServer.RemoteServerConfig == nil || mcpServer.RemoteServerConfig.ServiceRef == nil { + return nil + } + mcpServerLog.Debugf("ServiceRef %v for %v", mcpServer.RemoteServerConfig.ServiceRef, dataId) + configKey := strings.Join([]string{configGroup, dataId}, DefaultJoiner) + + serviceGroup := mcpServer.RemoteServerConfig.ServiceRef.GroupName + serviceNamespace := mcpServer.RemoteServerConfig.ServiceRef.NamespaceId + serviceName := mcpServer.RemoteServerConfig.ServiceRef.ServiceName + if serviceNamespace == "" { + serviceNamespace = provider.DefaultNacosServiceNamespace + } + // update config to service and unsubscribe old service + curSvcKey := strings.Join([]string{serviceNamespace, serviceGroup, serviceName}, DefaultJoiner) + if svcKey, exist := w.configToService[configKey]; exist && svcKey != curSvcKey { + split := strings.Split(svcKey, DefaultJoiner) + if svcCache, has := w.serviceCache[split[0]]; has { + if err := svcCache.RemoveListener(split[1], split[2], configKey); err != nil { + mcpServerLog.Errorf("remove listener error:%v", err) + } + } + } + w.configToService[configKey] = curSvcKey + + if _, exist := w.serviceCache[serviceNamespace]; !exist { + namingConfig := constant.NewClientConfig( + constant.WithTimeoutMs(DefaultNacosTimeout), + constant.WithLogLevel(DefaultNacosLogLevel), + constant.WithLogDir(DefaultNacosLogDir), + constant.WithCacheDir(DefaultNacosCacheDir), + constant.WithNotLoadCacheAtStart(DefaultNacosNotLoadCache), + constant.WithLogRollingConfig(&constant.ClientLogRollingConfig{ + MaxAge: DefaultNacosLogMaxAge, + }), + constant.WithUpdateCacheWhenEmpty(w.updateCacheWhenEmpty), + constant.WithNamespaceId(serviceNamespace), + constant.WithAccessKey(w.NacosAccessKey), + constant.WithSecretKey(w.NacosSecretKey), + ) + client, err := clients.NewNamingClient(vo.NacosClientParam{ + ClientConfig: namingConfig, + ServerConfigs: w.serverConfig, + }) + if err == nil { + w.serviceCache[serviceNamespace] = NewServiceCache(client) + } else { + return fmt.Errorf("can not create naming client err:%v", err) + } + } + svcCache := w.serviceCache[serviceNamespace] + err := svcCache.AddListener(serviceGroup, serviceName, configKey, w.getServiceCallback(mcpServer, configGroup, dataId)) + if err != nil { + return fmt.Errorf("add listener for dataId %v, service %s/%s error:%v", dataId, serviceGroup, serviceName, err) + } + return nil +} + +func (w *watcher) getServiceCallback(server *provider.McpServer, configGroup, dataId string) func(services []model.Instance) { + groupName := server.RemoteServerConfig.ServiceRef.GroupName + if groupName == "DEFAULT_GROUP" { + groupName = "DEFAULT-GROUP" + } + namespace := server.RemoteServerConfig.ServiceRef.NamespaceId + serviceName := server.RemoteServerConfig.ServiceRef.ServiceName + path := server.RemoteServerConfig.ExportPath + protocol := server.Protocol + + return func(services []model.Instance) { + defer w.UpdateService() + + configKey := strings.Join([]string{w.Name, w.NacosNamespace, configGroup, dataId}, DefaultJoiner) + + host := getNacosServiceFullHost(groupName, namespace, serviceName) + mcpServerLog.Infof("callback for %s/%s, serviceName : %s", configGroup, dataId, host) + + serviceEntry := w.generateServiceEntry(host, services) + se := &config.Config{ + Meta: config.Meta{ + GroupVersionKind: gvk.ServiceEntry, + Name: fmt.Sprintf("%s-%s-%s", provider.IstioMcpAutoGeneratedSeName, configGroup, strings.TrimSuffix(dataId, ".json")), + Namespace: w.namespace, + }, + Spec: serviceEntry, + } + if protocol == provider.McpSSEProtocol { + destinationRule := w.generateDrForSSEService(host) + dr := &config.Config{ + Meta: config.Meta{ + GroupVersionKind: gvk.DestinationRule, + Name: fmt.Sprintf("%s-%s-%s", provider.IstioMcpAutoGeneratedDrName, configGroup, strings.TrimSuffix(dataId, ".json")), + Namespace: w.namespace, + }, + Spec: destinationRule, + } + w.cache.UpdateConfigCache(gvk.DestinationRule, configKey, dr, false) + } + w.cache.UpdateConfigCache(gvk.ServiceEntry, configKey, se, false) + vs := w.buildVirtualServiceForMcpServer(serviceEntry, configGroup, dataId, path, server.Name) + w.cache.UpdateConfigCache(gvk.VirtualService, configKey, vs, false) + } +} + +func (w *watcher) buildVirtualServiceForMcpServer(serviceentry *v1alpha3.ServiceEntry, group, dataId, path, serverName string) *config.Config { + if serviceentry == nil { + return nil + } + hosts := w.McpServerExportDomains + if len(hosts) == 0 { + hosts = []string{"*"} + } + var gateways []string + for _, host := range hosts { + cleanHost := common2.CleanHost(host) + // namespace/name, name format: (istio cluster id)-host + gateways = append(gateways, w.namespace+"/"+ + common2.CreateConvertedName(w.clusterId, cleanHost), + common2.CreateConvertedName(constants.IstioIngressGatewayName, cleanHost)) + } + routeName := fmt.Sprintf("%s-%s-%s", provider.IstioMcpAutoGeneratedHttpRouteName, group, strings.TrimSuffix(dataId, ".json")) + mergePath := "/" + serverName + if w.McpServerBaseUrl != "/" { + mergePath = strings.TrimSuffix(w.McpServerBaseUrl, "/") + mergePath + } + if path != "/" { + mergePath = mergePath + "/" + strings.TrimPrefix(path, "/") + } + + vs := &v1alpha3.VirtualService{ + Hosts: hosts, + Gateways: gateways, + Http: []*v1alpha3.HTTPRoute{{ + Name: routeName, + Match: []*v1alpha3.HTTPMatchRequest{{ + Uri: &v1alpha3.StringMatch{ + MatchType: &v1alpha3.StringMatch_Prefix{ + Prefix: mergePath, + }, + }, + }}, + Rewrite: &v1alpha3.HTTPRewrite{ + Uri: path, + }, + Route: []*v1alpha3.HTTPRouteDestination{{ + Destination: &v1alpha3.Destination{ + Host: serviceentry.Hosts[0], + Port: &v1alpha3.PortSelector{ + Number: serviceentry.Ports[0].Number, + }, + }, + }}, + }}, + } + + mcpServerLog.Debugf("construct virtualservice %v", vs) + + return &config.Config{ + Meta: config.Meta{ + GroupVersionKind: gvk.VirtualService, + Name: fmt.Sprintf("%s-%s-%s", provider.IstioMcpAutoGeneratedVsName, group, dataId), + Namespace: w.namespace, + }, + Spec: vs, + } +} + +func (w *watcher) generateServiceEntry(host string, services []model.Instance) *v1alpha3.ServiceEntry { + portList := make([]*v1alpha3.ServicePort, 0) + endpoints := make([]*v1alpha3.WorkloadEntry, 0) + isDnsService := false + + for _, service := range services { + protocol := common.HTTP + if service.Metadata != nil && service.Metadata["protocol"] != "" { + protocol = common.ParseProtocol(service.Metadata["protocol"]) + } + port := &v1alpha3.ServicePort{ + Name: protocol.String(), + Number: uint32(service.Port), + Protocol: protocol.String(), + } + if len(portList) == 0 { + portList = append(portList, port) + } + if !isValidIP(service.Ip) { + isDnsService = true + } + endpoint := &v1alpha3.WorkloadEntry{ + Address: service.Ip, + Ports: map[string]uint32{port.Protocol: port.Number}, + Labels: service.Metadata, + } + endpoints = append(endpoints, endpoint) + } + + resolution := v1alpha3.ServiceEntry_STATIC + if isDnsService { + resolution = v1alpha3.ServiceEntry_DNS + } + se := &v1alpha3.ServiceEntry{ + Hosts: []string{host}, + Ports: portList, + Location: v1alpha3.ServiceEntry_MESH_INTERNAL, + Resolution: resolution, + Endpoints: endpoints, + } + + return se +} + +func (w *watcher) generateDrForSSEService(host string) *v1alpha3.DestinationRule { + dr := &v1alpha3.DestinationRule{ + Host: host, + TrafficPolicy: &v1alpha3.TrafficPolicy{ + LoadBalancer: &v1alpha3.LoadBalancerSettings{ + LbPolicy: &v1alpha3.LoadBalancerSettings_ConsistentHash{ + ConsistentHash: &v1alpha3.LoadBalancerSettings_ConsistentHashLB{ + HashKey: &v1alpha3.LoadBalancerSettings_ConsistentHashLB_UseSourceIp{ + UseSourceIp: true, + }, + }, + }, + }, + }, + } + return dr +} + +func parseMcpArgs(args interface{}) (*provider.ToolArgs, error) { + argsData, err := json.Marshal(args) + if err != nil { + return nil, err + } + toolArgs := &provider.ToolArgs{} + if err = json.Unmarshal(argsData, toolArgs); err != nil { + return nil, err + } + return toolArgs, nil +} + +func getArgsPositionFromToolMeta(toolMeta *provider.ToolsMeta) (map[string]string, error) { + result := map[string]string{} + if toolMeta == nil { + return result, nil + } + toolTemplate := toolMeta.Templates + for kind, meta := range toolTemplate { + switch kind { + case provider.JsonGoTemplateType: + templateData, err := json.Marshal(meta) + if err != nil { + return result, err + } + template := &provider.JsonGoTemplate{} + if err = json.Unmarshal(templateData, template); err != nil { + return result, err + } + result = mergeMaps(result, template.ArgsPosition) + default: + return result, fmt.Errorf("unsupport tool meta type %v", kind) + } + } + return result, nil +} + +func getRequestTemplateFromToolMeta(toolMeta *provider.ToolsMeta) (*provider.RequestTemplate, error) { + if toolMeta == nil { + return nil, nil + } + toolTemplate := toolMeta.Templates + for kind, meta := range toolTemplate { + switch kind { + case provider.JsonGoTemplateType: + templateData, err := json.Marshal(meta) + if err != nil { + return nil, err + } + template := &provider.JsonGoTemplate{} + if err = json.Unmarshal(templateData, template); err != nil { + return nil, err + } + return &template.RequestTemplate, nil + default: + return nil, fmt.Errorf("unsupport tool meta type") + } + } + return nil, nil +} + +func getResponseTemplateFromToolMeta(toolMeta *provider.ToolsMeta) (*provider.ResponseTemplate, error) { + if toolMeta == nil { + return nil, nil + } + toolTemplate := toolMeta.Templates + for kind, meta := range toolTemplate { + switch kind { + case provider.JsonGoTemplateType: + templateData, err := json.Marshal(meta) + if err != nil { + return nil, err + } + template := &provider.JsonGoTemplate{} + if err = json.Unmarshal(templateData, template); err != nil { + return nil, err + } + return &template.ResponseTemplate, nil + default: + return nil, fmt.Errorf("unsupport tool meta type") + } + } + return nil, nil +} + +func mergeMaps(maps ...map[string]string) map[string]string { + if len(maps) == 0 { + return nil + } + res := make(map[string]string, len(maps[0])) + for _, m := range maps { + for k, v := range m { + res[k] = v + } + } + return res +} + +func getNacosServiceFullHost(groupName, namespace, serviceName string) string { + suffix := strings.Join([]string{groupName, namespace, string(provider.Nacos)}, common.DotSeparator) + host := strings.Join([]string{serviceName, suffix}, common.DotSeparator) + return host +} + +func (w *watcher) Stop() { + w.mutex.Lock() + defer w.mutex.Unlock() + mcpServerLog.Infof("unsubscribe all configs") + for key := range w.watchingConfig { + s := strings.Split(key, DefaultJoiner) + err := w.unsubscribe(s[0], s[1]) + if err == nil { + delete(w.watchingConfig, key) + } + } + mcpServerLog.Infof("stop all service nameing client") + for _, client := range w.serviceCache { + client.Stop() + } + + w.isStop = true + mcpServerLog.Infof("stop all config client") + mcpServerLog.Infof("watcher %v stop", w.Name) + + close(w.stop) + w.Ready(false) +} + +func (w *watcher) IsHealthy() bool { + return w.Status == provider.Healthy +} + +func (w *watcher) GetRegistryType() string { + return w.RegistryType.String() +} + +func isValidIP(ipStr string) bool { + ip := net.ParseIP(ipStr) + return ip != nil +} diff --git a/registry/reconcile/reconcile.go b/registry/reconcile/reconcile.go index 21806d9e8..73c9c6625 100644 --- a/registry/reconcile/reconcile.go +++ b/registry/reconcile/reconcile.go @@ -23,6 +23,7 @@ import ( "sync" "time" + "github.com/alibaba/higress/registry/nacos/mcpserver" "istio.io/pkg/log" apiv1 "github.com/alibaba/higress/api/networking/v1" @@ -50,9 +51,10 @@ type Reconciler struct { serviceUpdate func() client kube.Client namespace string + clusterId string } -func NewReconciler(serviceUpdate func(), client kube.Client, namespace string) *Reconciler { +func NewReconciler(serviceUpdate func(), client kube.Client, namespace, clusterId string) *Reconciler { return &Reconciler{ Cache: memory.NewCache(), registries: make(map[string]*apiv1.RegistryConfig), @@ -60,6 +62,7 @@ func NewReconciler(serviceUpdate func(), client kube.Client, namespace string) * serviceUpdate: serviceUpdate, client: client, namespace: namespace, + clusterId: clusterId, } } @@ -183,6 +186,26 @@ func (r *Reconciler) generateWatcherFromRegistryConfig(registry *apiv1.RegistryC nacosv2.WithNacosRefreshInterval(registry.NacosRefreshInterval), nacosv2.WithAuthOption(authOption), ) + case string(Nacos3): + watcher, err = mcpserver.NewWatcher( + r.Cache, + mcpserver.WithType(registry.Type), + mcpserver.WithName(registry.Name), + mcpserver.WithNacosAddressServer(registry.NacosAddressServer), + mcpserver.WithDomain(registry.Domain), + mcpserver.WithPort(registry.Port), + mcpserver.WithNacosAccessKey(registry.NacosAccessKey), + mcpserver.WithNacosSecretKey(registry.NacosSecretKey), + mcpserver.WithNacosNamespaceId(registry.NacosNamespaceId), + mcpserver.WithNacosNamespace(registry.NacosNamespace), + mcpserver.WithNacosGroups(registry.NacosGroups), + mcpserver.WithNacosRefreshInterval(registry.NacosRefreshInterval), + mcpserver.WithMcpExportDomains(registry.McpServerExportDomains), + mcpserver.WithMcpBaseUrl(registry.McpServerBaseUrl), + mcpserver.WithEnableMcpServer(registry.EnableMCPServer), + mcpserver.WithClusterId(r.clusterId), + mcpserver.WithNamespace(r.namespace), + ) case string(Zookeeper): watcher, err = zookeeper.NewWatcher( r.Cache, diff --git a/registry/watcher.go b/registry/watcher.go index f43183b9d..7c664a63c 100644 --- a/registry/watcher.go +++ b/registry/watcher.go @@ -25,6 +25,7 @@ const ( Consul ServiceRegistryType = "consul" Nacos ServiceRegistryType = "nacos" Nacos2 ServiceRegistryType = "nacos2" + Nacos3 ServiceRegistryType = "nacos3" Static ServiceRegistryType = "static" DNS ServiceRegistryType = "dns" Healthy WatcherStatus = "healthy"