This is an automated email from the ASF dual-hosted git repository.

alexstocks pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/dubbo-go-pixiu.git


The following commit(s) were added to refs/heads/develop by this push:
     new 41826486 Control Panel: Feat/service metadata (#540)
41826486 is described below

commit 41826486bc82489b55eb2db31ad4260eeb44a6e6
Author: MasterKenway <[email protected]>
AuthorDate: Sun Jun 18 20:04:57 2023 +0800

    Control Panel: Feat/service metadata (#540)
    
    * feat: generate service metadata (with namespace)
---
 bin/update_crds.sh                                 |   8 +-
 go.mod                                             |   4 +-
 go.sum                                             |   8 +-
 manifests/charts/base/crds/crd-all.gen.yaml        | 118 ++++++++
 manifests/charts/base/files/gen-istio-cluster.yaml | 122 ++++++++-
 .../istio-discovery/files/gen-istio.yaml           |  20 +-
 .../istiod-remote/templates/crd-all.gen.yaml       | 118 ++++++++
 pilot/pkg/bootstrap/server.go                      |   9 +
 pilot/pkg/config/kube/crdclient/gen/main.go        |   1 +
 pilot/pkg/config/kube/crdclient/types.gen.go       |  52 ++++
 pilot/pkg/features/pilot.go                        |  22 ++
 pilot/pkg/model/push_context.go                    |  59 ++++
 pilot/pkg/model/push_context_test.go               |   2 +-
 .../networking/dubbo/v1alpha1/debouncehelper.go    | 150 ++++++++++
 .../dubbo/v1alpha1/servicemetadataserver.go        | 302 +++++++++++++++++++++
 .../schema/collections/collections.agent.gen.go    |  22 ++
 pkg/config/schema/collections/collections.gen.go   |  22 ++
 pkg/config/schema/gvk/resources.gen.go             |   1 +
 pkg/config/schema/metadata.yaml                    |  15 +
 19 files changed, 1032 insertions(+), 23 deletions(-)

diff --git a/bin/update_crds.sh b/bin/update_crds.sh
index 10d98d84..e329ab6b 100755
--- a/bin/update_crds.sh
+++ b/bin/update_crds.sh
@@ -29,14 +29,14 @@ SCRIPTPATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd 
)"
 ROOTDIR=$(dirname "${SCRIPTPATH}")
 cd "${ROOTDIR}"
 
-REPO="github.com/istio/api"
+REPO="github.com/MasterKenway/operator-api"
 # using the pseudo version we have in go.mod file. e.g. 
v.0.0.0-<timestamp>-<SHA>
 # first check if there's a replace: e.g. replace istio.io/api => 
github.com/USER/istioapi v0.0.0-<timestamp>-<SHA>
-SHA=$(grep "istio.io/api" go.mod | grep "^replace" | awk -F "-" '{print $NF}')
+SHA=$(grep "MasterKenway/operator-api" go.mod | grep "^replace" | awk -F "-" 
'{print $NF}')
 if [ -n "${SHA}" ]; then
-  REPO=$(grep "istio.io/api" go.mod | grep "^replace" | awk '{print $4}')
+  REPO=$(grep "MasterKenway/operator-api" go.mod | grep "^replace" | awk 
'{print $4}')
 else
-  SHA=$(grep "istio.io/api" go.mod | head -n1 | awk -F "-" '{print $NF}')
+  SHA=$(grep "MasterKenway/operator-api" go.mod | head -n1 | awk -F "-" 
'{print $NF}')
 fi
 
 if [ -z "${SHA}" ]; then
diff --git a/go.mod b/go.mod
index 78c17929..8d79c903 100644
--- a/go.mod
+++ b/go.mod
@@ -8,9 +8,9 @@ exclude k8s.io/kubernetes v1.13.0
 // Client-go does not handle different versions of mergo due to some breaking 
changes - use the matching version
 replace github.com/imdario/mergo => github.com/imdario/mergo v0.3.5
 
-replace istio.io/api => github.com/dubbo-go-pixiu/operator-api 
v0.0.0-20221126054223-dda83ac319f4
+replace istio.io/api => github.com/dubbo-go-pixiu/operator-api 
v0.0.0-20230521024122-de7669e54430
 
-replace istio.io/client-go => github.com/dubbo-go-pixiu/operator-client-go 
v1.14.6-0.20221126073212-882e30cca8f6
+replace istio.io/client-go => github.com/dubbo-go-pixiu/operator-client-go 
v1.14.6-0.20230521064746-0907a7fb8042
 
 require (
        cloud.google.com/go/compute v1.6.0
diff --git a/go.sum b/go.sum
index 703b27d8..fc74b32c 100644
--- a/go.sum
+++ b/go.sum
@@ -491,10 +491,10 @@ github.com/docker/go-units v0.4.0/go.mod 
h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
 github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod 
h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
 github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod 
h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
 github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod 
h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
-github.com/dubbo-go-pixiu/operator-api v0.0.0-20221126054223-dda83ac319f4 
h1:De7KZcGzrgYado/wA7PDBntNemkR7iYaO36gQagYYQY=
-github.com/dubbo-go-pixiu/operator-api 
v0.0.0-20221126054223-dda83ac319f4/go.mod 
h1:SWsRqLbdc3vFk5cIRylKHC28nx58hfikw2rjlpwR0Qs=
-github.com/dubbo-go-pixiu/operator-client-go 
v1.14.6-0.20221126073212-882e30cca8f6 
h1:GD5OCtBql9xl+UnWY+E7P4NCJBWyE+pE/JjY0jW+F8Y=
-github.com/dubbo-go-pixiu/operator-client-go 
v1.14.6-0.20221126073212-882e30cca8f6/go.mod 
h1:If8h1tIkt2k4P4hKsnQxR8m2bUF2hZBpfLP6VyaFO+g=
+github.com/dubbo-go-pixiu/operator-api v0.0.0-20230521024122-de7669e54430 
h1:2Qjn7n3kCSJ60VSNnpA/RbmeXcel6ftjhrN6opQ4SJQ=
+github.com/dubbo-go-pixiu/operator-api 
v0.0.0-20230521024122-de7669e54430/go.mod 
h1:SWsRqLbdc3vFk5cIRylKHC28nx58hfikw2rjlpwR0Qs=
+github.com/dubbo-go-pixiu/operator-client-go 
v1.14.6-0.20230521064746-0907a7fb8042 
h1:L/NmoQfKNhNObrfqVpit3q1DxN6hXn8N0EG9fCtDRaI=
+github.com/dubbo-go-pixiu/operator-client-go 
v1.14.6-0.20230521064746-0907a7fb8042/go.mod 
h1:PvtYHGhoKNXvyX9hPuScG/bQ2CLD47OSNvltcWmE8H4=
 github.com/dubbo-go-pixiu/pixiu-api v0.1.6-0.20220612115254-d9a176b25b99 
h1:UjDxgIEu6DbJVJTrxm5mwC0j54jNao1pkYVlT8X+KgY=
 github.com/dubbo-go-pixiu/pixiu-api 
v0.1.6-0.20220612115254-d9a176b25b99/go.mod 
h1:1l+6pDTdEHwCyyyJmfckOAdGp6f5PZ33ZVMgxso9q/U=
 github.com/dubbogo/go-zookeeper v1.0.3/go.mod 
h1:fn6n2CAEer3novYgk9ULLwAjuV8/g4DdC2ENwRb6E+c=
diff --git a/manifests/charts/base/crds/crd-all.gen.yaml 
b/manifests/charts/base/crds/crd-all.gen.yaml
index dc04ae97..31a291a0 100644
--- a/manifests/charts/base/crds/crd-all.gen.yaml
+++ b/manifests/charts/base/crds/crd-all.gen.yaml
@@ -1,6 +1,124 @@
 # DO NOT EDIT - Generated by Cue OpenAPI generator based on Istio APIs.
 apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
+metadata:
+  annotations:
+    "helm.sh/resource-policy": keep
+  labels:
+    app: istio-pilot
+    chart: istio
+    heritage: Tiller
+    release: istio
+  name: servicemetadatas.extensions.istio.io
+spec:
+  group: extensions.istio.io
+  names:
+    categories:
+    - istio-io
+    - extensions-istio-io
+    kind: ServiceMetadata
+    listKind: ServiceMetadataList
+    plural: servicemetadatas
+    shortNames:
+    - sm
+    singular: servicemetadata
+  scope: Namespaced
+  versions:
+  - additionalPrinterColumns:
+    - description: 'CreationTimestamp is a timestamp representing the server 
time
+        when this object was created. It is not guaranteed to be set in 
happens-before
+        order across separate operations. Clients may not set this value. It 
is represented
+        in RFC3339 form and is in UTC. Populated by the system. Read-only. 
Null for
+        lists. More info: 
https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata'
+      jsonPath: .metadata.creationTimestamp
+      name: Age
+      type: date
+    name: v1alpha1
+    schema:
+      openAPIV3Schema:
+        properties:
+          spec:
+            description: 'it''s the spec of Service Metadata See more details 
at:'
+            properties:
+              applicationName:
+                type: string
+              metadataInfo:
+                type: string
+              revision:
+                type: string
+            type: object
+          status:
+            type: object
+            x-kubernetes-preserve-unknown-fields: true
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
+
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    "helm.sh/resource-policy": keep
+  labels:
+    app: istio-pilot
+    chart: istio
+    heritage: Tiller
+    release: istio
+  name: servicenamemappings.extensions.istio.io
+spec:
+  group: extensions.istio.io
+  names:
+    categories:
+    - istio-io
+    - extensions-istio-io
+    kind: ServiceNameMapping
+    listKind: ServiceNameMappingList
+    plural: servicenamemappings
+    shortNames:
+    - snp
+    singular: servicenamemapping
+  scope: Namespaced
+  versions:
+  - additionalPrinterColumns:
+    - description: 'CreationTimestamp is a timestamp representing the server 
time
+        when this object was created. It is not guaranteed to be set in 
happens-before
+        order across separate operations. Clients may not set this value. It 
is represented
+        in RFC3339 form and is in UTC. Populated by the system. Read-only. 
Null for
+        lists. More info: 
https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata'
+      jsonPath: .metadata.creationTimestamp
+      name: Age
+      type: date
+    name: v1alpha1
+    schema:
+      openAPIV3Schema:
+        properties:
+          spec:
+            description: 'it''s the spec of Service Name Mapping, which is 
used to
+              map the interface name See more details at:'
+            properties:
+              applicationNames:
+                items:
+                  type: string
+                type: array
+              interfaceName:
+                description: InterfaceName.
+                type: string
+            type: object
+          status:
+            type: object
+            x-kubernetes-preserve-unknown-fields: true
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
+
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
 metadata:
   annotations:
     "helm.sh/resource-policy": keep
diff --git a/manifests/charts/base/files/gen-istio-cluster.yaml 
b/manifests/charts/base/files/gen-istio-cluster.yaml
index 06cd7846..dd40510a 100644
--- a/manifests/charts/base/files/gen-istio-cluster.yaml
+++ b/manifests/charts/base/files/gen-istio-cluster.yaml
@@ -3,6 +3,124 @@
 # DO NOT EDIT - Generated by Cue OpenAPI generator based on Istio APIs.
 apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
+metadata:
+  annotations:
+    "helm.sh/resource-policy": keep
+  labels:
+    app: istio-pilot
+    chart: istio
+    heritage: Tiller
+    release: istio
+  name: servicemetadatas.extensions.istio.io
+spec:
+  group: extensions.istio.io
+  names:
+    categories:
+    - istio-io
+    - extensions-istio-io
+    kind: ServiceMetadata
+    listKind: ServiceMetadataList
+    plural: servicemetadatas
+    shortNames:
+    - sm
+    singular: servicemetadata
+  scope: Namespaced
+  versions:
+  - additionalPrinterColumns:
+    - description: 'CreationTimestamp is a timestamp representing the server 
time
+        when this object was created. It is not guaranteed to be set in 
happens-before
+        order across separate operations. Clients may not set this value. It 
is represented
+        in RFC3339 form and is in UTC. Populated by the system. Read-only. 
Null for
+        lists. More info: 
https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata'
+      jsonPath: .metadata.creationTimestamp
+      name: Age
+      type: date
+    name: v1alpha1
+    schema:
+      openAPIV3Schema:
+        properties:
+          spec:
+            description: 'it''s the spec of Service Metadata See more details 
at:'
+            properties:
+              applicationName:
+                type: string
+              metadataInfo:
+                type: string
+              revision:
+                type: string
+            type: object
+          status:
+            type: object
+            x-kubernetes-preserve-unknown-fields: true
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
+
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    "helm.sh/resource-policy": keep
+  labels:
+    app: istio-pilot
+    chart: istio
+    heritage: Tiller
+    release: istio
+  name: servicenamemappings.extensions.istio.io
+spec:
+  group: extensions.istio.io
+  names:
+    categories:
+    - istio-io
+    - extensions-istio-io
+    kind: ServiceNameMapping
+    listKind: ServiceNameMappingList
+    plural: servicenamemappings
+    shortNames:
+    - snp
+    singular: servicenamemapping
+  scope: Namespaced
+  versions:
+  - additionalPrinterColumns:
+    - description: 'CreationTimestamp is a timestamp representing the server 
time
+        when this object was created. It is not guaranteed to be set in 
happens-before
+        order across separate operations. Clients may not set this value. It 
is represented
+        in RFC3339 form and is in UTC. Populated by the system. Read-only. 
Null for
+        lists. More info: 
https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata'
+      jsonPath: .metadata.creationTimestamp
+      name: Age
+      type: date
+    name: v1alpha1
+    schema:
+      openAPIV3Schema:
+        properties:
+          spec:
+            description: 'it''s the spec of Service Name Mapping, which is 
used to
+              map the interface name See more details at:'
+            properties:
+              applicationNames:
+                items:
+                  type: string
+                type: array
+              interfaceName:
+                description: InterfaceName.
+                type: string
+            type: object
+          status:
+            type: object
+            x-kubernetes-preserve-unknown-fields: true
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
+
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
 metadata:
   annotations:
     "helm.sh/resource-policy": keep
@@ -6842,14 +6960,14 @@ subjects:
 ---
 # Source: base/templates/default.yaml
 apiVersion: admissionregistration.k8s.io/v1
-kind: ValidatingWebhookConfiguration
+kind: Val:wqidatingWebhookConfiguration
 metadata:
   name: istiod-default-validator
   labels:
     app: istiod
     release: istio
     istio: istiod
-    istio.io/rev: default
+    istio.io/rev: dubbo
 webhooks:
   - name: validation.istio.io
     clientConfig:
diff --git 
a/manifests/charts/istio-control/istio-discovery/files/gen-istio.yaml 
b/manifests/charts/istio-control/istio-discovery/files/gen-istio.yaml
index 5c6579b8..aef1c70d 100644
--- a/manifests/charts/istio-control/istio-discovery/files/gen-istio.yaml
+++ b/manifests/charts/istio-control/istio-discovery/files/gen-istio.yaml
@@ -1886,7 +1886,7 @@ metadata:
   labels:
     istio.io/rev: default
 spec:
-  
+
   configPatches:
     - applyTo: HTTP_FILTER
       match:
@@ -2011,7 +2011,7 @@ metadata:
   labels:
     istio.io/rev: default
 spec:
-  
+
   configPatches:
     - applyTo: NETWORK_FILTER
       match:
@@ -2128,7 +2128,7 @@ metadata:
   labels:
     istio.io/rev: default
 spec:
-  
+
   configPatches:
     - applyTo: HTTP_FILTER
       match:
@@ -2253,7 +2253,7 @@ metadata:
   labels:
     istio.io/rev: default
 spec:
-  
+
   configPatches:
     - applyTo: NETWORK_FILTER
       match:
@@ -2370,7 +2370,7 @@ metadata:
   labels:
     istio.io/rev: default
 spec:
-  
+
   configPatches:
     - applyTo: HTTP_FILTER
       match:
@@ -2495,7 +2495,7 @@ metadata:
   labels:
     istio.io/rev: default
 spec:
-  
+
   configPatches:
     - applyTo: NETWORK_FILTER
       match:
@@ -2612,7 +2612,7 @@ metadata:
   labels:
     istio.io/rev: default
 spec:
-  
+
   configPatches:
     - applyTo: HTTP_FILTER
       match:
@@ -2737,7 +2737,7 @@ metadata:
   labels:
     istio.io/rev: default
 spec:
-  
+
   configPatches:
     - applyTo: NETWORK_FILTER
       match:
@@ -2854,7 +2854,7 @@ metadata:
   labels:
     istio.io/rev: default
 spec:
-  
+
   configPatches:
     - applyTo: HTTP_FILTER
       match:
@@ -2979,7 +2979,7 @@ metadata:
   labels:
     istio.io/rev: default
 spec:
-  
+
   configPatches:
     - applyTo: NETWORK_FILTER
       match:
diff --git a/manifests/charts/istiod-remote/templates/crd-all.gen.yaml 
b/manifests/charts/istiod-remote/templates/crd-all.gen.yaml
index f5f05073..5c623cf6 100644
--- a/manifests/charts/istiod-remote/templates/crd-all.gen.yaml
+++ b/manifests/charts/istiod-remote/templates/crd-all.gen.yaml
@@ -2,6 +2,124 @@
 # DO NOT EDIT - Generated by Cue OpenAPI generator based on Istio APIs.
 apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
+metadata:
+  annotations:
+    "helm.sh/resource-policy": keep
+  labels:
+    app: istio-pilot
+    chart: istio
+    heritage: Tiller
+    release: istio
+  name: servicemetadatas.extensions.istio.io
+spec:
+  group: extensions.istio.io
+  names:
+    categories:
+    - istio-io
+    - extensions-istio-io
+    kind: ServiceMetadata
+    listKind: ServiceMetadataList
+    plural: servicemetadatas
+    shortNames:
+    - sm
+    singular: servicemetadata
+  scope: Namespaced
+  versions:
+  - additionalPrinterColumns:
+    - description: 'CreationTimestamp is a timestamp representing the server 
time
+        when this object was created. It is not guaranteed to be set in 
happens-before
+        order across separate operations. Clients may not set this value. It 
is represented
+        in RFC3339 form and is in UTC. Populated by the system. Read-only. 
Null for
+        lists. More info: 
https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata'
+      jsonPath: .metadata.creationTimestamp
+      name: Age
+      type: date
+    name: v1alpha1
+    schema:
+      openAPIV3Schema:
+        properties:
+          spec:
+            description: 'it''s the spec of Service Metadata See more details 
at:'
+            properties:
+              applicationName:
+                type: string
+              metadataInfo:
+                type: string
+              revision:
+                type: string
+            type: object
+          status:
+            type: object
+            x-kubernetes-preserve-unknown-fields: true
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
+
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    "helm.sh/resource-policy": keep
+  labels:
+    app: istio-pilot
+    chart: istio
+    heritage: Tiller
+    release: istio
+  name: servicenamemappings.extensions.istio.io
+spec:
+  group: extensions.istio.io
+  names:
+    categories:
+    - istio-io
+    - extensions-istio-io
+    kind: ServiceNameMapping
+    listKind: ServiceNameMappingList
+    plural: servicenamemappings
+    shortNames:
+    - snp
+    singular: servicenamemapping
+  scope: Namespaced
+  versions:
+  - additionalPrinterColumns:
+    - description: 'CreationTimestamp is a timestamp representing the server 
time
+        when this object was created. It is not guaranteed to be set in 
happens-before
+        order across separate operations. Clients may not set this value. It 
is represented
+        in RFC3339 form and is in UTC. Populated by the system. Read-only. 
Null for
+        lists. More info: 
https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata'
+      jsonPath: .metadata.creationTimestamp
+      name: Age
+      type: date
+    name: v1alpha1
+    schema:
+      openAPIV3Schema:
+        properties:
+          spec:
+            description: 'it''s the spec of Service Name Mapping, which is 
used to
+              map the interface name See more details at:'
+            properties:
+              applicationNames:
+                items:
+                  type: string
+                type: array
+              interfaceName:
+                description: InterfaceName.
+                type: string
+            type: object
+          status:
+            type: object
+            x-kubernetes-preserve-unknown-fields: true
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
+
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
 metadata:
   annotations:
     "helm.sh/resource-policy": keep
diff --git a/pilot/pkg/bootstrap/server.go b/pilot/pkg/bootstrap/server.go
index fc2da178..59305712 100644
--- a/pilot/pkg/bootstrap/server.go
+++ b/pilot/pkg/bootstrap/server.go
@@ -180,6 +180,9 @@ type Server struct {
        // RWConfigStore is the configstore which allows updates, particularly 
for status.
        RWConfigStore model.ConfigStoreController
 
+       // ServiceMetadataServer
+       metadataServer *dubbov1alpha1.ServiceMetadataServer
+
        //Service Name mapping register
        snpServer *dubbov1alpha1.Snp
 }
@@ -277,6 +280,10 @@ func NewServer(args *PilotArgs, initFuncs 
...func(*Server)) (*Server, error) {
                return nil, err
        }
 
+       if s.kubeClient != nil {
+               s.metadataServer = 
dubbov1alpha1.NewServiceMetadataServer(s.environment, s.kubeClient)
+       }
+
        // Create Service Name mapping server
        if s.kubeClient != nil {
                s.snpServer = dubbov1alpha1.NewSnp(s.kubeClient)
@@ -745,6 +752,7 @@ func (s *Server) initGrpcServer(options 
*istiokeepalive.Options) {
        s.grpcServer = grpc.NewServer(grpcOptions...)
        s.XDSServer.Register(s.grpcServer)
        reflection.Register(s.grpcServer)
+       s.metadataServer.Register(s.grpcServer)
        s.snpServer.Register(s.grpcServer)
 }
 
@@ -794,6 +802,7 @@ func (s *Server) initSecureDiscoveryService(args 
*PilotArgs) error {
        s.secureGrpcServer = grpc.NewServer(opts...)
        s.XDSServer.Register(s.secureGrpcServer)
        reflection.Register(s.secureGrpcServer)
+       s.metadataServer.Register(s.secureGrpcServer)
        s.snpServer.Register(s.secureGrpcServer)
 
        s.addStartFunc(func(stop <-chan struct{}) error {
diff --git a/pilot/pkg/config/kube/crdclient/gen/main.go 
b/pilot/pkg/config/kube/crdclient/gen/main.go
index fac0e734..01e621d8 100644
--- a/pilot/pkg/config/kube/crdclient/gen/main.go
+++ b/pilot/pkg/config/kube/crdclient/gen/main.go
@@ -164,6 +164,7 @@ var (
                "nodes":                         "Nodes",
                "secrets":                       "Secrets",
                "ingresses":                     "Ingresses",
+               "servicemetadatas":              "ServiceMetadatas",
                "servicenamemappings":           "ServiceNameMappings",
        }
 
diff --git a/pilot/pkg/config/kube/crdclient/types.gen.go 
b/pilot/pkg/config/kube/crdclient/types.gen.go
index 728a56d5..a842439f 100644
--- a/pilot/pkg/config/kube/crdclient/types.gen.go
+++ b/pilot/pkg/config/kube/crdclient/types.gen.go
@@ -58,6 +58,11 @@ import (
 
 func create(ic versionedclient.Interface, sc gatewayapiclient.Interface, cfg 
config.Config, objMeta metav1.ObjectMeta) (metav1.Object, error) {
        switch cfg.GroupVersionKind {
+       case 
collections.IstioExtensionsV1Alpha1Servicemetadatas.Resource().GroupVersionKind():
+               return 
ic.ExtensionsV1alpha1().ServiceMetadatas(cfg.Namespace).Create(context.TODO(), 
&clientextensionsv1alpha1.ServiceMetadata{
+                       ObjectMeta: objMeta,
+                       Spec:       
*(cfg.Spec.(*extensionsv1alpha1.ServiceMetadata)),
+               }, metav1.CreateOptions{})
        case 
collections.IstioExtensionsV1Alpha1Servicenamemappings.Resource().GroupVersionKind():
                return 
ic.ExtensionsV1alpha1().ServiceNameMappings(cfg.Namespace).Create(context.TODO(),
 &clientextensionsv1alpha1.ServiceNameMapping{
                        ObjectMeta: objMeta,
@@ -170,6 +175,11 @@ func create(ic versionedclient.Interface, sc 
gatewayapiclient.Interface, cfg con
 
 func update(ic versionedclient.Interface, sc gatewayapiclient.Interface, cfg 
config.Config, objMeta metav1.ObjectMeta) (metav1.Object, error) {
        switch cfg.GroupVersionKind {
+       case 
collections.IstioExtensionsV1Alpha1Servicemetadatas.Resource().GroupVersionKind():
+               return 
ic.ExtensionsV1alpha1().ServiceMetadatas(cfg.Namespace).Update(context.TODO(), 
&clientextensionsv1alpha1.ServiceMetadata{
+                       ObjectMeta: objMeta,
+                       Spec:       
*(cfg.Spec.(*extensionsv1alpha1.ServiceMetadata)),
+               }, metav1.UpdateOptions{})
        case 
collections.IstioExtensionsV1Alpha1Servicenamemappings.Resource().GroupVersionKind():
                return 
ic.ExtensionsV1alpha1().ServiceNameMappings(cfg.Namespace).Update(context.TODO(),
 &clientextensionsv1alpha1.ServiceNameMapping{
                        ObjectMeta: objMeta,
@@ -283,6 +293,12 @@ func update(ic versionedclient.Interface, sc 
gatewayapiclient.Interface, cfg con
 func updateStatus(ic versionedclient.Interface, sc gatewayapiclient.Interface, 
cfg config.Config, objMeta metav1.ObjectMeta) (metav1.Object, error) {
        switch cfg.GroupVersionKind {
 
+       case 
collections.IstioExtensionsV1Alpha1Servicemetadatas.Resource().GroupVersionKind():
+               return 
ic.ExtensionsV1alpha1().ServiceMetadatas(cfg.Namespace).UpdateStatus(context.TODO(),
 &clientextensionsv1alpha1.ServiceMetadata{
+                       ObjectMeta: objMeta,
+                       Status:     *(cfg.Status.(*metav1alpha1.IstioStatus)),
+               }, metav1.UpdateOptions{})
+
        case 
collections.IstioExtensionsV1Alpha1Servicenamemappings.Resource().GroupVersionKind():
                return 
ic.ExtensionsV1alpha1().ServiceNameMappings(cfg.Namespace).UpdateStatus(context.TODO(),
 &clientextensionsv1alpha1.ServiceNameMapping{
                        ObjectMeta: objMeta,
@@ -413,6 +429,21 @@ func patch(ic versionedclient.Interface, sc 
gatewayapiclient.Interface, orig con
        }
        // TODO support setting field manager
        switch orig.GroupVersionKind {
+       case 
collections.IstioExtensionsV1Alpha1Servicemetadatas.Resource().GroupVersionKind():
+               oldRes := &clientextensionsv1alpha1.ServiceMetadata{
+                       ObjectMeta: origMeta,
+                       Spec:       
*(orig.Spec.(*extensionsv1alpha1.ServiceMetadata)),
+               }
+               modRes := &clientextensionsv1alpha1.ServiceMetadata{
+                       ObjectMeta: modMeta,
+                       Spec:       
*(mod.Spec.(*extensionsv1alpha1.ServiceMetadata)),
+               }
+               patchBytes, err := genPatchBytes(oldRes, modRes, typ)
+               if err != nil {
+                       return nil, err
+               }
+               return ic.ExtensionsV1alpha1().ServiceMetadatas(orig.Namespace).
+                       Patch(context.TODO(), orig.Name, typ, patchBytes, 
metav1.PatchOptions{FieldManager: "pilot-discovery"})
        case 
collections.IstioExtensionsV1Alpha1Servicenamemappings.Resource().GroupVersionKind():
                oldRes := &clientextensionsv1alpha1.ServiceNameMapping{
                        ObjectMeta: origMeta,
@@ -739,6 +770,8 @@ func delete(ic versionedclient.Interface, sc 
gatewayapiclient.Interface, typ con
                deleteOptions.Preconditions = 
&metav1.Preconditions{ResourceVersion: resourceVersion}
        }
        switch typ {
+       case 
collections.IstioExtensionsV1Alpha1Servicemetadatas.Resource().GroupVersionKind():
+               return 
ic.ExtensionsV1alpha1().ServiceMetadatas(namespace).Delete(context.TODO(), 
name, deleteOptions)
        case 
collections.IstioExtensionsV1Alpha1Servicenamemappings.Resource().GroupVersionKind():
                return 
ic.ExtensionsV1alpha1().ServiceNameMappings(namespace).Delete(context.TODO(), 
name, deleteOptions)
        case 
collections.IstioExtensionsV1Alpha1Wasmplugins.Resource().GroupVersionKind():
@@ -787,6 +820,25 @@ func delete(ic versionedclient.Interface, sc 
gatewayapiclient.Interface, typ con
 }
 
 var translationMap = map[config.GroupVersionKind]func(r runtime.Object) 
config.Config{
+       
collections.IstioExtensionsV1Alpha1Servicemetadatas.Resource().GroupVersionKind():
 func(r runtime.Object) config.Config {
+               obj := r.(*clientextensionsv1alpha1.ServiceMetadata)
+               return config.Config{
+                       Meta: config.Meta{
+                               GroupVersionKind:  
collections.IstioExtensionsV1Alpha1Servicemetadatas.Resource().GroupVersionKind(),
+                               Name:              obj.Name,
+                               Namespace:         obj.Namespace,
+                               Labels:            obj.Labels,
+                               Annotations:       obj.Annotations,
+                               ResourceVersion:   obj.ResourceVersion,
+                               CreationTimestamp: obj.CreationTimestamp.Time,
+                               OwnerReferences:   obj.OwnerReferences,
+                               UID:               string(obj.UID),
+                               Generation:        obj.Generation,
+                       },
+                       Spec:   &obj.Spec,
+                       Status: &obj.Status,
+               }
+       },
        
collections.IstioExtensionsV1Alpha1Servicenamemappings.Resource().GroupVersionKind():
 func(r runtime.Object) config.Config {
                obj := r.(*clientextensionsv1alpha1.ServiceNameMapping)
                return config.Config{
diff --git a/pilot/pkg/features/pilot.go b/pilot/pkg/features/pilot.go
index 484c1055..c3392a42 100644
--- a/pilot/pkg/features/pilot.go
+++ b/pilot/pkg/features/pilot.go
@@ -110,6 +110,28 @@ var (
                        " EDS pushes may be delayed, but there will be fewer 
pushes. By default this is enabled",
        ).Get()
 
+       SMDebounceAfter = env.RegisterDurationVar(
+               "PILOT_SM_DEBOUNCE_AFTER",
+               100*time.Millisecond,
+               "The delay added to config/registry events for debouncing. This 
will delay the push by "+
+                       "at least this interval. If no change is detected 
within this period, the push will happen, "+
+                       " otherwise we'll keep delaying until things settle, up 
to a max of PILOT_SM_DEBOUNCE_MAX.",
+       ).Get()
+
+       SMDebounceMax = env.RegisterDurationVar(
+               "PILOT_SM_DEBOUNCE_MAX",
+               1*time.Second,
+               "The maximum amount of time to wait for events while 
debouncing. If events keep showing up with no breaks "+
+                       "for this time, we'll trigger a push.",
+       ).Get()
+
+       SMEnableDebounce = env.RegisterBoolVar(
+               "PILOT_SM_ENABLE_DEBOUNCE",
+               true,
+               "If enabled, Pilot will include EDS pushes in the push 
debouncing, configured by PILOT_SM_DEBOUNCE_AFTER and PILOT_SM_DEBOUNCE_MAX."+
+                       " SNP register may be delayed, but there will be fewer 
pushes. By default this is enabled",
+       ).Get()
+
        SNPDebounceAfter = env.RegisterDurationVar(
                "PILOT_SNP_DEBOUNCE_AFTER",
                100*time.Millisecond,
diff --git a/pilot/pkg/model/push_context.go b/pilot/pkg/model/push_context.go
index f7a3076d..695abd9b 100644
--- a/pilot/pkg/model/push_context.go
+++ b/pilot/pkg/model/push_context.go
@@ -173,6 +173,21 @@ func newGatewayIndex() gatewayIndex {
        }
 }
 
+// serviceMetadataIndex is the index of service metadata by various fields.
+type serviceMetadataIndex struct {
+       namespace                  map[string][]*config.Config
+       applicationNameByNamespace map[string]map[string]*config.Config
+       all                        []*config.Config
+}
+
+func newServiceMetadataIndex() serviceMetadataIndex {
+       return serviceMetadataIndex{
+               namespace:                  map[string][]*config.Config{},
+               applicationNameByNamespace: 
map[string]map[string]*config.Config{},
+               all:                        []*config.Config{},
+       }
+}
+
 // serviceNameMappingIndex is the index of Service Name Mapping by various 
fields.
 type serviceNameMappingIndex struct {
        // namespace contains Service Name Mapping by namespace.
@@ -226,6 +241,9 @@ type PushContext struct {
        // sidecarIndex stores sidecar resources
        sidecarIndex sidecarIndex
 
+       // serviceMetadataIndex stores service metadata resources
+       serviceMetadataIndex serviceMetadataIndex
+
        // serviceNameMappingIndex is the index of service name mapping.
        serviceNameMappingIndex serviceNameMappingIndex
 
@@ -660,6 +678,7 @@ func NewPushContext() *PushContext {
                gatewayIndex:            newGatewayIndex(),
                ProxyStatus:             
map[string]map[string]ProxyPushStatus{},
                ServiceAccounts:         map[host.Name]map[int][]string{},
+               serviceMetadataIndex:    newServiceMetadataIndex(),
                serviceNameMappingIndex: newServiceNameMappingIndex(),
        }
 }
@@ -1117,6 +1136,13 @@ func (ps *PushContext) 
getExportedDestinationRuleFromNamespace(owningNamespace s
        return nil
 }
 
+func (ps *PushContext) ServiceMetadata(namespace, applicationName, revision 
string) *config.Config {
+       if conf, ok := 
ps.serviceMetadataIndex.applicationNameByNamespace[namespace][strings.ToLower(fmt.Sprintf("%s-%s",
 applicationName, revision))]; ok {
+               return conf
+       }
+       return nil
+}
+
 // IsClusterLocal indicates whether the endpoints for the service should only 
be accessible to clients
 // within the cluster.
 func (ps *PushContext) IsClusterLocal(service *Service) bool {
@@ -1211,6 +1237,10 @@ func (ps *PushContext) createNewContext(env 
*Environment) error {
                return err
        }
 
+       if err := ps.initServiceMetadata(env); err != nil {
+               return err
+       }
+
        // Must be initialized in the end
        if err := ps.initSidecarScopes(env); err != nil {
                return err
@@ -2007,6 +2037,35 @@ func (ps *PushContext) initGateways(env *Environment) 
error {
        return nil
 }
 
+func (ps *PushContext) initServiceMetadata(env *Environment) error {
+       metadataConfig, err := env.List(gvk.ServiceMetadata, NamespaceAll)
+       if err != nil {
+               return err
+       }
+
+       sortConfigByCreationTime(metadataConfig)
+
+       ps.serviceMetadataIndex.namespace = make(map[string][]*config.Config)
+       ps.serviceMetadataIndex.applicationNameByNamespace = 
make(map[string]map[string]*config.Config)
+       ps.serviceMetadataIndex.all = make([]*config.Config, 0)
+
+       for _, conf := range metadataConfig {
+               if _, ok := ps.serviceMetadataIndex.namespace[conf.Namespace]; 
!ok {
+                       ps.serviceMetadataIndex.namespace[conf.Namespace] = 
make([]*config.Config, 0)
+               }
+
+               if _, ok := 
ps.serviceMetadataIndex.applicationNameByNamespace[conf.Namespace]; !ok {
+                       
ps.serviceMetadataIndex.applicationNameByNamespace[conf.Namespace] = 
make(map[string]*config.Config, 0)
+               }
+
+               ps.serviceMetadataIndex.namespace[conf.Namespace] = 
append(ps.serviceMetadataIndex.namespace[conf.Namespace], &conf)
+               
ps.serviceMetadataIndex.applicationNameByNamespace[conf.Namespace][conf.Name] = 
&conf
+               ps.serviceMetadataIndex.all = 
append(ps.serviceMetadataIndex.all, &conf)
+       }
+
+       return nil
+}
+
 // InternalGatewayServiceAnnotation represents the hostname of the service a 
gateway will use. This is
 // only used internally to transfer information from the Kubernetes Gateway 
API to the Istio Gateway API
 // which does not have a field to represent this.
diff --git a/pilot/pkg/model/push_context_test.go 
b/pilot/pkg/model/push_context_test.go
index cdf6c068..ee152d84 100644
--- a/pilot/pkg/model/push_context_test.go
+++ b/pilot/pkg/model/push_context_test.go
@@ -949,7 +949,7 @@ func TestInitPushContext(t *testing.T) {
                // Allow looking into exported fields for parts of push context
                cmp.AllowUnexported(PushContext{}, exportToDefaults{}, 
serviceIndex{}, virtualServiceIndex{},
                        destinationRuleIndex{}, gatewayIndex{}, 
consolidatedDestRules{}, IstioEgressListenerWrapper{}, SidecarScope{},
-                       AuthenticationPolicies{}, NetworkManager{}, 
sidecarIndex{}, Telemetries{}, ProxyConfigs{}, consolidatedDestRule{}, 
serviceNameMappingIndex{}),
+                       AuthenticationPolicies{}, NetworkManager{}, 
sidecarIndex{}, Telemetries{}, ProxyConfigs{}, consolidatedDestRule{}, 
serviceNameMappingIndex{}, serviceMetadataIndex{}),
                // These are not feasible/worth comparing
                cmpopts.IgnoreTypes(sync.RWMutex{}, localServiceDiscovery{}, 
FakeStore{}, atomic.Bool{}, sync.Mutex{}),
                cmpopts.IgnoreInterfaces(struct{ mesh.Holder }{}),
diff --git a/pilot/pkg/networking/dubbo/v1alpha1/debouncehelper.go 
b/pilot/pkg/networking/dubbo/v1alpha1/debouncehelper.go
new file mode 100644
index 00000000..2e9ff409
--- /dev/null
+++ b/pilot/pkg/networking/dubbo/v1alpha1/debouncehelper.go
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 v1alpha1
+
+import (
+       "fmt"
+       "time"
+)
+
+import (
+       "go.uber.org/atomic"
+)
+
+/**
+Copy From pilot/pkg/xds/discovery.go
+*/
+type debounceOptions struct {
+       // debounceAfter is the delay added to events to wait
+       // after a registry/config event for debouncing.
+       // This will delay the push by at least this interval, plus
+       // the time getting subsequent events. If no change is
+       // detected the push will happen, otherwise we'll keep
+       // delaying until things settle.
+       debounceAfter time.Duration
+
+       // debounceMax is the maximum time to wait for events
+       // while debouncing. Defaults to 10 seconds. If events keep
+       // showing up with no break for this time, we'll trigger a push.
+       debounceMax time.Duration
+
+       // enableDebounce indicates whether EDS pushes should be debounced.
+       enableDebounce bool
+}
+
+type DebounceHelper struct {
+}
+
+func (h *DebounceHelper) Debounce(ch chan *pushRequest, stopCh <-chan 
struct{}, opts debounceOptions, pushFn func(req *pushRequest), updateSent 
*atomic.Int64) {
+       defer func() {
+               err := recover()
+               if err != nil {
+                       log.Infof("Debounce panic caused by: {%+v}", err)
+               }
+       }()
+
+       var timeChan <-chan time.Time
+       var startDebounce time.Time
+       var lastConfigUpdateTime time.Time
+
+       pushCounter := 0
+       debouncedEvents := 0
+
+       // Keeps track of the push requests. If updates are debounce they will 
be merged.
+       var req *pushRequest
+
+       free := true
+       freeCh := make(chan struct{}, 1)
+
+       push := func(req *pushRequest, debouncedEvents int) {
+               pushFn(req)
+               updateSent.Add(int64(debouncedEvents))
+               freeCh <- struct{}{}
+       }
+
+       pushWorker := func() {
+               eventDelay := time.Since(startDebounce)
+               quietTime := time.Since(lastConfigUpdateTime)
+               // it has been too long or quiet enough
+               if eventDelay >= opts.debounceMax || quietTime >= 
opts.debounceAfter {
+                       if req != nil {
+                               pushCounter++
+                               if req.ConfigsUpdated == nil {
+                                       log.Infof("Push debounce stable[%d] %d 
: %v since last change, %v since last push",
+                                               pushCounter, debouncedEvents,
+                                               quietTime, eventDelay)
+                               } else {
+                                       log.Infof("Push debounce stable[%d] %d 
for config %s: %v since last change, %v since last push",
+                                               pushCounter, debouncedEvents, 
PushRequestConfigsUpdated(req),
+                                               quietTime, eventDelay)
+                               }
+                               free = false
+                               go push(req, debouncedEvents)
+                               req = nil
+                               debouncedEvents = 0
+                       }
+               } else {
+                       timeChan = time.After(opts.debounceAfter - quietTime)
+               }
+       }
+
+       for {
+               select {
+               case <-freeCh:
+                       free = true
+                       pushWorker()
+               case r := <-ch:
+                       if !opts.enableDebounce {
+                               // trigger push now, just for EDS
+                               go func(req *pushRequest) {
+                                       pushFn(req)
+                                       updateSent.Inc()
+                               }(r)
+                               continue
+                       }
+
+                       lastConfigUpdateTime = time.Now()
+                       if debouncedEvents == 0 {
+                               timeChan = time.After(opts.debounceAfter)
+                               startDebounce = lastConfigUpdateTime
+                       }
+                       debouncedEvents++
+
+                       req = req.Merge(r)
+               case <-timeChan:
+                       if free {
+                               pushWorker()
+                       }
+               case <-stopCh:
+                       return
+               }
+       }
+}
+
+func PushRequestConfigsUpdated(req *pushRequest) string {
+       configs := ""
+       for key := range req.ConfigsUpdated {
+               configs += key.String()
+               break
+       }
+       if len(req.ConfigsUpdated) > 1 {
+               more := fmt.Sprintf(" and %d more configs", 
len(req.ConfigsUpdated)-1)
+               configs += more
+       }
+       return configs
+}
diff --git a/pilot/pkg/networking/dubbo/v1alpha1/servicemetadataserver.go 
b/pilot/pkg/networking/dubbo/v1alpha1/servicemetadataserver.go
new file mode 100644
index 00000000..4907035e
--- /dev/null
+++ b/pilot/pkg/networking/dubbo/v1alpha1/servicemetadataserver.go
@@ -0,0 +1,302 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 v1alpha1
+
+import (
+       "context"
+       "fmt"
+       "strconv"
+       "time"
+)
+
+import (
+       "github.com/pkg/errors"
+       "go.uber.org/atomic"
+       "google.golang.org/grpc"
+       "google.golang.org/grpc/codes"
+       "google.golang.org/grpc/status"
+       dubbov1alpha1 "istio.io/api/dubbo/v1alpha1"
+       istioextensionsv1alpha1 "istio.io/api/extensions/v1alpha1"
+       "istio.io/client-go/pkg/apis/extensions/v1alpha1"
+       apierror "k8s.io/apimachinery/pkg/api/errors"
+       v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+import (
+       "github.com/apache/dubbo-go-pixiu/pilot/pkg/features"
+       "github.com/apache/dubbo-go-pixiu/pilot/pkg/model"
+       "github.com/apache/dubbo-go-pixiu/pkg/kube"
+)
+
+type ServiceMetadataServer struct {
+       dubbov1alpha1.UnimplementedServiceMetadataServiceServer
+
+       KubeClient kube.Client
+
+       // CommittedUpdates describes the number of configuration updates the 
discovery server has
+       // received, process, and stored in the push context. If this number is 
less than InboundUpdates,
+       // there are updates we have not yet processed.
+       // Note: This does not mean that all proxies have received these 
configurations; it is strictly
+       // the push context, which means that the next push to a proxy will 
receive this configuration.
+       CommittedUpdates *atomic.Int64
+
+       ch chan *pushRequest
+
+       debounceOptions debounceOptions
+       debounceHelper  DebounceHelper
+}
+
+type pushRequest struct {
+       // application name + revision => metadata info
+       ConfigsUpdated map[model.ConfigKey]metadataConfig
+}
+
+type metadataConfig struct {
+       applicationName string
+       revision        string
+       metadataInfo    string
+       timestamp       time.Time
+}
+
+func (pr *pushRequest) Merge(other *pushRequest) *pushRequest {
+       if pr == nil {
+               return other
+       }
+       if other == nil {
+               return pr
+       }
+
+       // Keep the first (older) start time
+
+       // Do not merge when any one is empty
+       if len(pr.ConfigsUpdated) == 0 || len(other.ConfigsUpdated) == 0 {
+               pr.ConfigsUpdated = nil
+       } else {
+               for key, value := range other.ConfigsUpdated {
+                       if prValue, ok := pr.ConfigsUpdated[key]; ok {
+                               if value.timestamp.After(prValue.timestamp) {
+                                       pr.ConfigsUpdated[key] = value
+                               }
+                       } else {
+                               pr.ConfigsUpdated[key] = value
+                       }
+               }
+       }
+
+       return pr
+}
+
+func NewServiceMetadataServer(env *model.Environment, client kube.Client) 
*ServiceMetadataServer {
+       return &ServiceMetadataServer{
+               CommittedUpdates: atomic.NewInt64(0),
+               KubeClient:       client,
+               ch:               make(chan *pushRequest, 10),
+               debounceOptions: debounceOptions{
+                       debounceAfter:  features.SMDebounceAfter,
+                       debounceMax:    features.SMDebounceMax,
+                       enableDebounce: features.SMEnableDebounce,
+               },
+       }
+}
+
+func (s *ServiceMetadataServer) Start(stopCh <-chan struct{}) {
+       go s.handleUpdate(stopCh)
+       go s.removeOutdatedCRD(stopCh)
+}
+
+func (s *ServiceMetadataServer) Register(rpcs *grpc.Server) {
+       // Register v3 server
+       dubbov1alpha1.RegisterServiceMetadataServiceServer(rpcs, s)
+}
+
+func (s *ServiceMetadataServer) Publish(ctx context.Context, request 
*dubbov1alpha1.PublishServiceMetadataRequest) 
(*dubbov1alpha1.PublishServiceMetadataResponse, error) {
+       if exists, err := s.isNamespaceExists(ctx, request.GetNamespace()); err 
!= nil {
+               if !exists {
+                       return nil, status.Errorf(codes.Aborted, "Namespace Not 
Exists")
+               }
+       } else {
+               return nil, status.Errorf(codes.Aborted, err.Error())
+       }
+
+       pushReq := &pushRequest{ConfigsUpdated: 
map[model.ConfigKey]metadataConfig{}}
+
+       key := model.ConfigKey{
+               Name:      getConfigKeyName(request.GetApplicationName(), 
request.GetRevision()),
+               Namespace: request.GetNamespace(),
+       }
+
+       pushReq.ConfigsUpdated[key] = metadataConfig{
+               applicationName: request.GetApplicationName(),
+               revision:        request.GetRevision(),
+               metadataInfo:    request.GetMetadataInfo(),
+               timestamp:       time.Now(),
+       }
+
+       s.ch <- pushReq
+
+       return &dubbov1alpha1.PublishServiceMetadataResponse{}, nil
+}
+
+func getConfigKeyName(applicationName, revision string) string {
+       log.Infof("application name: %s, revision: %s", applicationName, 
revision)
+       return fmt.Sprintf("%s-%s", applicationName, revision)
+}
+
+func (s *ServiceMetadataServer) Get(ctx context.Context, request 
*dubbov1alpha1.GetServiceMetadataRequest) 
(*dubbov1alpha1.GetServiceMetadataResponse, error) {
+       metadata, err := 
s.KubeClient.Istio().ExtensionsV1alpha1().ServiceMetadatas(request.Namespace).Get(ctx,
 getConfigKeyName(request.GetApplicationName(), request.GetRevision()), 
v1.GetOptions{})
+       if err != nil {
+               return nil, status.Errorf(codes.Aborted, err.Error())
+       }
+
+       return &dubbov1alpha1.GetServiceMetadataResponse{MetadataInfo: 
metadata.Spec.GetMetadataInfo()}, nil
+}
+
+func (s *ServiceMetadataServer) isNamespaceExists(ctx context.Context, 
namespace string) (bool, error) {
+       _, err := s.KubeClient.Kube().CoreV1().Namespaces().Get(ctx, namespace, 
v1.GetOptions{})
+       if err != nil {
+               if !apierror.IsNotFound(err) {
+                       return false, err
+               }
+       }
+       return true, nil
+}
+
+func (s *ServiceMetadataServer) handleUpdate(stopCh <-chan struct{}) {
+       s.debounce(stopCh)
+}
+
+func (s *ServiceMetadataServer) Push(req *pushRequest) {
+       ctx := context.TODO()
+
+       for key, config := range req.ConfigsUpdated {
+               metadata, err := getOrCreateCRD(s.KubeClient, ctx, 
key.Namespace, config.applicationName, config.revision, config.metadataInfo, 
config.timestamp)
+               if err != nil {
+                       log.Errorf("Failed to getOrCreateCRD, %s", err.Error())
+                       return
+               }
+
+               metadata.Spec.MetadataInfo = config.metadataInfo
+
+               _, err = 
s.KubeClient.Istio().ExtensionsV1alpha1().ServiceMetadatas(metadata.Namespace).Update(ctx,
 metadata, v1.UpdateOptions{})
+               if err != nil {
+                       log.Errorf("Failed to update metadata, %s", err.Error())
+                       return
+               }
+       }
+}
+
+func getOrCreateCRD(kubeClient kube.Client, ctx context.Context, namespace 
string, applicationName, revision, metadataInfo string, createTime time.Time) 
(*v1alpha1.ServiceMetadata, error) {
+       var (
+               metadata *v1alpha1.ServiceMetadata
+       )
+
+       serviceMetadataInterface := 
kubeClient.Istio().ExtensionsV1alpha1().ServiceMetadatas(namespace)
+       crdName := getConfigKeyName(applicationName, revision)
+       metadata, err := serviceMetadataInterface.Get(ctx, crdName, 
v1.GetOptions{})
+
+       log.Infof("metadata: %+v", metadata)
+
+       if err != nil {
+               if apierror.IsNotFound(err) {
+                       mt := &v1alpha1.ServiceMetadata{
+                               ObjectMeta: v1.ObjectMeta{
+                                       Name:      crdName,
+                                       Namespace: namespace,
+                                       Annotations: map[string]string{
+                                               "updateTime": 
strconv.FormatInt(createTime.Unix(), 10),
+                                       },
+                               },
+                               Spec: istioextensionsv1alpha1.ServiceMetadata{
+                                       ApplicationName: applicationName,
+                                       Revision:        revision,
+                                       MetadataInfo:    metadataInfo,
+                               },
+                       }
+
+                       metadata, err = serviceMetadataInterface.Create(ctx, 
mt, v1.CreateOptions{})
+                       if err != nil {
+                               return nil, errors.Wrap(err, "Failed to create 
metadata")
+                       }
+               } else {
+                       return nil, errors.Wrap(err, "Failed to check metadata 
exists or not")
+               }
+       } else {
+               // if metadata exist in crd, then update timestamp
+               if metadata.ObjectMeta.Annotations != nil {
+                       metadata.ObjectMeta.Annotations["updateTime"] = 
strconv.FormatInt(createTime.Unix(), 10)
+               } else {
+                       metadata.ObjectMeta.Annotations = map[string]string{
+                               "updateTime": 
strconv.FormatInt(createTime.Unix(), 10),
+                       }
+               }
+       }
+
+       return metadata, nil
+}
+
+func (s *ServiceMetadataServer) debounce(stopCh <-chan struct{}) {
+       s.debounceHelper.Debounce(s.ch, stopCh, s.debounceOptions, s.Push, 
s.CommittedUpdates)
+}
+
+func (s *ServiceMetadataServer) removeOutdatedCRD(stopChan <-chan struct{}) {
+       ctx, cancel := context.WithCancel(context.TODO())
+       ticker := time.NewTicker(2 * 60 * time.Hour)
+
+       for {
+               select {
+               case <-stopChan:
+                       cancel()
+                       break
+               case <-ticker.C:
+                       namespaces, err := 
s.KubeClient.Kube().CoreV1().Namespaces().List(ctx, v1.ListOptions{})
+                       if err != nil {
+                               log.Errorf("Failed to get namespaces, %s", 
err.Error())
+                               continue
+                       }
+
+                       for _, namespace := range namespaces.Items {
+                               metadataList, err := 
s.KubeClient.Istio().ExtensionsV1alpha1().ServiceMetadatas(namespace.GetNamespace()).List(ctx,
 v1.ListOptions{})
+                               if err != nil {
+                                       log.Errorf("Failed to get metadata with 
namespace {%s}, %s", namespace, err.Error())
+                                       continue
+                               }
+
+                               for _, metadata := range metadataList.Items {
+                                       if metadata.Annotations != nil {
+                                               if ts, ok := 
metadata.Annotations["updateTime"]; ok {
+                                                       tsInt, err := 
strconv.ParseInt(ts, 10, 64)
+                                                       if err != nil {
+                                                               
log.Errorf("Failed to get metadata with namespace {%s}, %s", namespace, 
err.Error())
+                                                       } else {
+                                                               if 
time.Now().Sub(time.Unix(tsInt, 0)) > 24*time.Hour {
+                                                                       err := 
s.KubeClient.Istio().ExtensionsV1alpha1().ServiceMetadatas(namespace.GetNamespace()).Delete(ctx,
 metadata.GetName(), v1.DeleteOptions{})
+                                                                       if err 
!= nil {
+                                                                               
log.Errorf("Failed to delete outdated metadata with namespace {%s}, metadata 
{%s}, err {%s}", namespace, metadata.GetName(), err.Error())
+                                                                       }
+                                                               }
+                                                       }
+                                               } else {
+                                                       log.Errorf("Failed to 
get metadata with namespace {%s}, metadata {%s}", namespace, metadata.GetName())
+                                               }
+                                       }
+                               }
+                       }
+
+               }
+       }
+}
diff --git a/pkg/config/schema/collections/collections.agent.gen.go 
b/pkg/config/schema/collections/collections.agent.gen.go
index 7fc72567..4b0ac04d 100755
--- a/pkg/config/schema/collections/collections.agent.gen.go
+++ b/pkg/config/schema/collections/collections.agent.gen.go
@@ -28,6 +28,24 @@ import (
 
 var (
 
+       // IstioExtensionsV1Alpha1Servicemetadatas describes the collection
+       // istio/extensions/v1alpha1/servicemetadatas
+       IstioExtensionsV1Alpha1Servicemetadatas = collection.Builder{
+               Name:         "istio/extensions/v1alpha1/servicemetadatas",
+               VariableName: "IstioExtensionsV1Alpha1Servicemetadatas",
+               Resource: resource.Builder{
+                       Group:   "extensions.istio.io",
+                       Kind:    "ServiceMetadata",
+                       Plural:  "servicemetadatas",
+                       Version: "v1alpha1",
+                       Proto:   "istio.extensions.v1alpha1.ServiceMetadata", 
StatusProto: "istio.meta.v1alpha1.IstioStatus",
+                       ReflectType: 
reflect.TypeOf(&istioioapiextensionsv1alpha1.ServiceMetadata{}).Elem(), 
StatusType: reflect.TypeOf(&istioioapimetav1alpha1.IstioStatus{}).Elem(),
+                       ProtoPackage: "istio.io/api/extensions/v1alpha1", 
StatusPackage: "istio.io/api/meta/v1alpha1",
+                       ClusterScoped: false,
+                       ValidateProto: validation.EmptyValidate,
+               }.MustBuild(),
+       }.MustBuild()
+
        // IstioExtensionsV1Alpha1Servicenamemappings describes the collection
        // istio/extensions/v1alpha1/servicenamemappings
        IstioExtensionsV1Alpha1Servicenamemappings = collection.Builder{
@@ -336,6 +354,7 @@ var (
 
        // All contains all collections in the system.
        All = collection.NewSchemasBuilder().
+               MustAdd(IstioExtensionsV1Alpha1Servicemetadatas).
                MustAdd(IstioExtensionsV1Alpha1Servicenamemappings).
                MustAdd(IstioExtensionsV1Alpha1Wasmplugins).
                MustAdd(IstioMeshV1Alpha1MeshConfig).
@@ -358,6 +377,7 @@ var (
        // Istio contains only Istio collections.
        Istio = collection.NewSchemasBuilder().
                MustAdd(IstioExtensionsV1Alpha1Servicenamemappings).
+               MustAdd(IstioExtensionsV1Alpha1Servicemetadatas).
                MustAdd(IstioExtensionsV1Alpha1Wasmplugins).
                MustAdd(IstioMeshV1Alpha1MeshConfig).
                MustAdd(IstioMeshV1Alpha1MeshNetworks).
@@ -387,6 +407,7 @@ var (
 
        // Pilot contains only collections used by Pilot.
        Pilot = collection.NewSchemasBuilder().
+               MustAdd(IstioExtensionsV1Alpha1Servicemetadatas).
                MustAdd(IstioExtensionsV1Alpha1Servicenamemappings).
                MustAdd(IstioExtensionsV1Alpha1Wasmplugins).
                MustAdd(IstioNetworkingV1Alpha3Destinationrules).
@@ -406,6 +427,7 @@ var (
 
        // PilotGatewayAPI contains only collections used by Pilot, including 
experimental Service Api.
        PilotGatewayAPI = collection.NewSchemasBuilder().
+                       MustAdd(IstioExtensionsV1Alpha1Servicemetadatas).
                        MustAdd(IstioExtensionsV1Alpha1Servicenamemappings).
                        MustAdd(IstioExtensionsV1Alpha1Wasmplugins).
                        MustAdd(IstioNetworkingV1Alpha3Destinationrules).
diff --git a/pkg/config/schema/collections/collections.gen.go 
b/pkg/config/schema/collections/collections.gen.go
index df9d0fa5..45fa0bb9 100755
--- a/pkg/config/schema/collections/collections.gen.go
+++ b/pkg/config/schema/collections/collections.gen.go
@@ -34,6 +34,24 @@ import (
 
 var (
 
+       // IstioExtensionsV1Alpha1Servicemetadatas describes the collection
+       // istio/extensions/v1alpha1/servicemetadatas
+       IstioExtensionsV1Alpha1Servicemetadatas = collection.Builder{
+               Name:         "istio/extensions/v1alpha1/servicemetadatas",
+               VariableName: "IstioExtensionsV1Alpha1Servicemetadatas",
+               Resource: resource.Builder{
+                       Group:   "extensions.istio.io",
+                       Kind:    "ServiceMetadata",
+                       Plural:  "servicemetadatas",
+                       Version: "v1alpha1",
+                       Proto:   "istio.extensions.v1alpha1.ServiceMetadata", 
StatusProto: "istio.meta.v1alpha1.IstioStatus",
+                       ReflectType: 
reflect.TypeOf(&istioioapiextensionsv1alpha1.ServiceMetadata{}).Elem(), 
StatusType: reflect.TypeOf(&istioioapimetav1alpha1.IstioStatus{}).Elem(),
+                       ProtoPackage: "istio.io/api/extensions/v1alpha1", 
StatusPackage: "istio.io/api/meta/v1alpha1",
+                       ClusterScoped: false,
+                       ValidateProto: validation.EmptyValidate,
+               }.MustBuild(),
+       }.MustBuild()
+
        // IstioExtensionsV1Alpha1Servicenamemappings describes the collection
        // istio/extensions/v1alpha1/servicenamemappings
        IstioExtensionsV1Alpha1Servicenamemappings = collection.Builder{
@@ -641,6 +659,7 @@ var (
 
        // All contains all collections in the system.
        All = collection.NewSchemasBuilder().
+               MustAdd(IstioExtensionsV1Alpha1Servicemetadatas).
                MustAdd(IstioExtensionsV1Alpha1Servicenamemappings).
                MustAdd(IstioExtensionsV1Alpha1Wasmplugins).
                MustAdd(IstioMeshV1Alpha1MeshConfig).
@@ -679,6 +698,7 @@ var (
 
        // Istio contains only Istio collections.
        Istio = collection.NewSchemasBuilder().
+               MustAdd(IstioExtensionsV1Alpha1Servicemetadatas).
                MustAdd(IstioExtensionsV1Alpha1Servicenamemappings).
                MustAdd(IstioExtensionsV1Alpha1Wasmplugins).
                MustAdd(IstioMeshV1Alpha1MeshConfig).
@@ -737,6 +757,7 @@ var (
 
        // Pilot contains only collections used by Pilot.
        Pilot = collection.NewSchemasBuilder().
+               MustAdd(IstioExtensionsV1Alpha1Servicemetadatas).
                MustAdd(IstioExtensionsV1Alpha1Servicenamemappings).
                MustAdd(IstioExtensionsV1Alpha1Wasmplugins).
                MustAdd(IstioNetworkingV1Alpha3Destinationrules).
@@ -756,6 +777,7 @@ var (
 
        // PilotGatewayAPI contains only collections used by Pilot, including 
experimental Service Api.
        PilotGatewayAPI = collection.NewSchemasBuilder().
+                       MustAdd(IstioExtensionsV1Alpha1Servicemetadatas).
                        MustAdd(IstioExtensionsV1Alpha1Servicenamemappings).
                        MustAdd(IstioExtensionsV1Alpha1Wasmplugins).
                        MustAdd(IstioNetworkingV1Alpha3Destinationrules).
diff --git a/pkg/config/schema/gvk/resources.gen.go 
b/pkg/config/schema/gvk/resources.gen.go
index 20703626..a52a9f88 100755
--- a/pkg/config/schema/gvk/resources.gen.go
+++ b/pkg/config/schema/gvk/resources.gen.go
@@ -33,6 +33,7 @@ var (
        Secret                       = config.GroupVersionKind{Group: "", 
Version: "v1", Kind: "Secret"}
        Service                      = config.GroupVersionKind{Group: "", 
Version: "v1", Kind: "Service"}
        ServiceEntry                 = config.GroupVersionKind{Group: 
"networking.istio.io", Version: "v1alpha3", Kind: "ServiceEntry"}
+       ServiceMetadata              = config.GroupVersionKind{Group: 
"extensions.istio.io", Version: "v1alpha1", Kind: "ServiceMetadata"}
        ServiceNameMapping           = config.GroupVersionKind{Group: 
"extensions.istio.io", Version: "v1alpha1", Kind: "ServiceNameMapping"}
        Sidecar                      = config.GroupVersionKind{Group: 
"networking.istio.io", Version: "v1alpha3", Kind: "Sidecar"}
        TCPRoute                     = config.GroupVersionKind{Group: 
"gateway.networking.k8s.io", Version: "v1alpha2", Kind: "TCPRoute"}
diff --git a/pkg/config/schema/metadata.yaml b/pkg/config/schema/metadata.yaml
index 9815f135..d1c0d965 100644
--- a/pkg/config/schema/metadata.yaml
+++ b/pkg/config/schema/metadata.yaml
@@ -180,6 +180,11 @@ collections:
     name: "k8s/gateway_api/v1alpha2/referencepolicies"
     group: "gateway.networking.k8s.io"
 
+  - kind: "ServiceMetadata"
+    name: "istio/extensions/v1alpha1/servicemetadatas"
+    group: "extensions.istio.io"
+    pilot: true
+
   - name: "istio/extensions/v1alpha1/servicenamemappings"
     kind: ServiceNameMapping
     group: "extensions.istio.io"
@@ -474,6 +479,16 @@ resources:
     statusProto: "istio.meta.v1alpha1.IstioStatus"
     statusProtoPackage: "istio.io/api/meta/v1alpha1"
 
+  - kind: ServiceMetadata
+    plural: "servicemetadatas"
+    group: "extensions.istio.io"
+    version: "v1alpha1"
+    proto: "istio.extensions.v1alpha1.ServiceMetadata"
+    protoPackage: "istio.io/api/extensions/v1alpha1"
+    description: "describes service metadata"
+    statusProto: "istio.meta.v1alpha1.IstioStatus"
+    statusProtoPackage: "istio.io/api/meta/v1alpha1"
+
   - kind: ServiceNameMapping
     plural: "servicenamemappings"
     group: "extensions.istio.io"

Reply via email to