This is an automated email from the ASF dual-hosted git repository.
mfordjody pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/dubbo-kubernetes.git
The following commit(s) were added to refs/heads/master by this push:
new 0b60a259 feat(discovery): add ServiceEntry registry (#953)
0b60a259 is described below
commit 0b60a25939faa007f723f2bbce553dc25153842a
Author: mfordjody <[email protected]>
AuthorDate: Mon Jul 13 19:30:03 2026 +0800
feat(discovery): add ServiceEntry registry (#953)
* Update ci infra v2
* Validate unlabeled telemetry resources
* feat(discovery): add ServiceEntry registry
---
.../discovery/pkg/bootstrap/service_controller.go | 6 +
.../pkg/config/kube/crdclient/types.gen.go | 102 +++++++
.../pkg/serviceregistry/aggregate/controller.go | 34 ++-
.../serviceregistry/aggregate/controller_test.go | 69 +++++
.../pkg/serviceregistry/provider/provider.go | 2 +
.../pkg/serviceregistry/serviceentry/controller.go | 311 +++++++++++++++++++
.../serviceentry/controller_test.go | 197 ++++++++++++
go.mod | 4 +-
go.sum | 8 +-
manifests/charts/base/files/crd-all.gen.yaml | 335 +++++++++++++++++++++
pkg/config/protocol/instance.go | 12 +
pkg/config/schema/collections/collections.gen.go | 38 +++
pkg/config/schema/gvk/resources.gen.go | 14 +
pkg/config/schema/gvr/resources.gen.go | 6 +
pkg/config/schema/kind/resources.gen.go | 10 +
pkg/config/schema/kubeclient/resources.gen.go | 26 ++
pkg/config/schema/kubetypes/resources.gen.go | 8 +
pkg/config/schema/metadata.yaml | 18 ++
pkg/config/validation/validators.go | 124 ++++++++
pkg/config/validation/validators_test.go | 62 ++++
20 files changed, 1377 insertions(+), 9 deletions(-)
diff --git a/dubbod/discovery/pkg/bootstrap/service_controller.go
b/dubbod/discovery/pkg/bootstrap/service_controller.go
index 6a555e8f..caf2915e 100644
--- a/dubbod/discovery/pkg/bootstrap/service_controller.go
+++ b/dubbod/discovery/pkg/bootstrap/service_controller.go
@@ -24,6 +24,7 @@ import (
"github.com/apache/dubbo-kubernetes/dubbod/discovery/pkg/serviceregistry/aggregate"
kubecontroller
"github.com/apache/dubbo-kubernetes/dubbod/discovery/pkg/serviceregistry/kube/controller"
"github.com/apache/dubbo-kubernetes/dubbod/discovery/pkg/serviceregistry/provider"
+
"github.com/apache/dubbo-kubernetes/dubbod/discovery/pkg/serviceregistry/serviceentry"
"github.com/apache/dubbo-kubernetes/pkg/util/sets"
)
@@ -52,6 +53,11 @@ func (s *Server) initServiceControllers(args *DubboArgs)
error {
return fmt.Errorf("service registry %s is not
supported", r)
}
}
+
serviceControllers.AddRegistryAndRun(serviceentry.NewController(serviceentry.Options{
+ ConfigController: s.configController,
+ XDSUpdater: s.XDSServer,
+ ClusterID: s.clusterID,
+ }), s.internalStop)
s.addStartFunc("service controllers", func(stop <-chan struct{}) error {
go serviceControllers.Run(stop)
diff --git a/dubbod/discovery/pkg/config/kube/crdclient/types.gen.go
b/dubbod/discovery/pkg/config/kube/crdclient/types.gen.go
index ef1064fc..818efced 100755
--- a/dubbod/discovery/pkg/config/kube/crdclient/types.gen.go
+++ b/dubbod/discovery/pkg/config/kube/crdclient/types.gen.go
@@ -80,11 +80,21 @@ func create(c kube.Client, cfg config.Config, objMeta
metav1.ObjectMeta) (metav1
ObjectMeta: objMeta,
Spec:
*(cfg.Spec.(*githubcomkdubboapisecurityv1alpha3.RequestAuthentication)),
}, metav1.CreateOptions{})
+ case gvk.ServiceEntry:
+ return
c.Dubbo().NetworkingV1alpha3().ServiceEntries(cfg.Namespace).Create(context.TODO(),
&apigithubcomapachedubbokubernetesapinetworkingv1alpha3.ServiceEntry{
+ ObjectMeta: objMeta,
+ Spec:
*(cfg.Spec.(*githubcomkdubboapinetworkingv1alpha3.ServiceEntry)),
+ }, metav1.CreateOptions{})
case gvk.Telemetry:
return
c.Dubbo().TelemetryV1alpha1().Telemetries(cfg.Namespace).Create(context.TODO(),
&apigithubcomapachedubbokubernetesapitelemetryv1alpha1.Telemetry{
ObjectMeta: objMeta,
Spec:
*(cfg.Spec.(*githubcomkdubboapitelemetryv1alpha1.Telemetry)),
}, metav1.CreateOptions{})
+ case gvk.WorkloadEntry:
+ return
c.Dubbo().NetworkingV1alpha3().WorkloadEntries(cfg.Namespace).Create(context.TODO(),
&apigithubcomapachedubbokubernetesapinetworkingv1alpha3.WorkloadEntry{
+ ObjectMeta: objMeta,
+ Spec:
*(cfg.Spec.(*githubcomkdubboapinetworkingv1alpha3.WorkloadEntry)),
+ }, metav1.CreateOptions{})
default:
return nil, fmt.Errorf("unsupported type: %v",
cfg.GroupVersionKind)
}
@@ -137,11 +147,21 @@ func update(c kube.Client, cfg config.Config, objMeta
metav1.ObjectMeta) (metav1
ObjectMeta: objMeta,
Spec:
*(cfg.Spec.(*githubcomkdubboapisecurityv1alpha3.RequestAuthentication)),
}, metav1.UpdateOptions{})
+ case gvk.ServiceEntry:
+ return
c.Dubbo().NetworkingV1alpha3().ServiceEntries(cfg.Namespace).Update(context.TODO(),
&apigithubcomapachedubbokubernetesapinetworkingv1alpha3.ServiceEntry{
+ ObjectMeta: objMeta,
+ Spec:
*(cfg.Spec.(*githubcomkdubboapinetworkingv1alpha3.ServiceEntry)),
+ }, metav1.UpdateOptions{})
case gvk.Telemetry:
return
c.Dubbo().TelemetryV1alpha1().Telemetries(cfg.Namespace).Update(context.TODO(),
&apigithubcomapachedubbokubernetesapitelemetryv1alpha1.Telemetry{
ObjectMeta: objMeta,
Spec:
*(cfg.Spec.(*githubcomkdubboapitelemetryv1alpha1.Telemetry)),
}, metav1.UpdateOptions{})
+ case gvk.WorkloadEntry:
+ return
c.Dubbo().NetworkingV1alpha3().WorkloadEntries(cfg.Namespace).Update(context.TODO(),
&apigithubcomapachedubbokubernetesapinetworkingv1alpha3.WorkloadEntry{
+ ObjectMeta: objMeta,
+ Spec:
*(cfg.Spec.(*githubcomkdubboapinetworkingv1alpha3.WorkloadEntry)),
+ }, metav1.UpdateOptions{})
default:
return nil, fmt.Errorf("unsupported type: %v",
cfg.GroupVersionKind)
}
@@ -189,11 +209,21 @@ func updateStatus(c kube.Client, cfg config.Config,
objMeta metav1.ObjectMeta) (
ObjectMeta: objMeta,
Status:
*(cfg.Status.(*githubcomkdubboapimetav1alpha1.DubboStatus)),
}, metav1.UpdateOptions{})
+ case gvk.ServiceEntry:
+ return
c.Dubbo().NetworkingV1alpha3().ServiceEntries(cfg.Namespace).UpdateStatus(context.TODO(),
&apigithubcomapachedubbokubernetesapinetworkingv1alpha3.ServiceEntry{
+ ObjectMeta: objMeta,
+ Status:
*(cfg.Status.(*githubcomkdubboapimetav1alpha1.DubboStatus)),
+ }, metav1.UpdateOptions{})
case gvk.Telemetry:
return
c.Dubbo().TelemetryV1alpha1().Telemetries(cfg.Namespace).UpdateStatus(context.TODO(),
&apigithubcomapachedubbokubernetesapitelemetryv1alpha1.Telemetry{
ObjectMeta: objMeta,
Status:
*(cfg.Status.(*githubcomkdubboapimetav1alpha1.DubboStatus)),
}, metav1.UpdateOptions{})
+ case gvk.WorkloadEntry:
+ return
c.Dubbo().NetworkingV1alpha3().WorkloadEntries(cfg.Namespace).UpdateStatus(context.TODO(),
&apigithubcomapachedubbokubernetesapinetworkingv1alpha3.WorkloadEntry{
+ ObjectMeta: objMeta,
+ Status:
*(cfg.Status.(*githubcomkdubboapimetav1alpha1.DubboStatus)),
+ }, metav1.UpdateOptions{})
default:
return nil, fmt.Errorf("unsupported type: %v",
cfg.GroupVersionKind)
}
@@ -339,6 +369,21 @@ func patch(c kube.Client, orig config.Config, origMeta
metav1.ObjectMeta, mod co
}
return
c.Dubbo().SecurityV1alpha3().RequestAuthentications(orig.Namespace).
Patch(context.TODO(), orig.Name, typ, patchBytes,
metav1.PatchOptions{FieldManager: "pilot-discovery"})
+ case gvk.ServiceEntry:
+ oldRes :=
&apigithubcomapachedubbokubernetesapinetworkingv1alpha3.ServiceEntry{
+ ObjectMeta: origMeta,
+ Spec:
*(orig.Spec.(*githubcomkdubboapinetworkingv1alpha3.ServiceEntry)),
+ }
+ modRes :=
&apigithubcomapachedubbokubernetesapinetworkingv1alpha3.ServiceEntry{
+ ObjectMeta: modMeta,
+ Spec:
*(mod.Spec.(*githubcomkdubboapinetworkingv1alpha3.ServiceEntry)),
+ }
+ patchBytes, err := genPatchBytes(oldRes, modRes, typ)
+ if err != nil {
+ return nil, err
+ }
+ return
c.Dubbo().NetworkingV1alpha3().ServiceEntries(orig.Namespace).
+ Patch(context.TODO(), orig.Name, typ, patchBytes,
metav1.PatchOptions{FieldManager: "pilot-discovery"})
case gvk.Telemetry:
oldRes :=
&apigithubcomapachedubbokubernetesapitelemetryv1alpha1.Telemetry{
ObjectMeta: origMeta,
@@ -354,6 +399,21 @@ func patch(c kube.Client, orig config.Config, origMeta
metav1.ObjectMeta, mod co
}
return
c.Dubbo().TelemetryV1alpha1().Telemetries(orig.Namespace).
Patch(context.TODO(), orig.Name, typ, patchBytes,
metav1.PatchOptions{FieldManager: "pilot-discovery"})
+ case gvk.WorkloadEntry:
+ oldRes :=
&apigithubcomapachedubbokubernetesapinetworkingv1alpha3.WorkloadEntry{
+ ObjectMeta: origMeta,
+ Spec:
*(orig.Spec.(*githubcomkdubboapinetworkingv1alpha3.WorkloadEntry)),
+ }
+ modRes :=
&apigithubcomapachedubbokubernetesapinetworkingv1alpha3.WorkloadEntry{
+ ObjectMeta: modMeta,
+ Spec:
*(mod.Spec.(*githubcomkdubboapinetworkingv1alpha3.WorkloadEntry)),
+ }
+ patchBytes, err := genPatchBytes(oldRes, modRes, typ)
+ if err != nil {
+ return nil, err
+ }
+ return
c.Dubbo().NetworkingV1alpha3().WorkloadEntries(orig.Namespace).
+ Patch(context.TODO(), orig.Name, typ, patchBytes,
metav1.PatchOptions{FieldManager: "pilot-discovery"})
default:
return nil, fmt.Errorf("unsupported type: %v",
orig.GroupVersionKind)
}
@@ -383,8 +443,12 @@ func delete(c kube.Client, typ config.GroupVersionKind,
name, namespace string,
return
c.GatewayAPI().GatewayV1beta1().ReferenceGrants(namespace).Delete(context.TODO(),
name, deleteOptions)
case gvk.RequestAuthentication:
return
c.Dubbo().SecurityV1alpha3().RequestAuthentications(namespace).Delete(context.TODO(),
name, deleteOptions)
+ case gvk.ServiceEntry:
+ return
c.Dubbo().NetworkingV1alpha3().ServiceEntries(namespace).Delete(context.TODO(),
name, deleteOptions)
case gvk.Telemetry:
return
c.Dubbo().TelemetryV1alpha1().Telemetries(namespace).Delete(context.TODO(),
name, deleteOptions)
+ case gvk.WorkloadEntry:
+ return
c.Dubbo().NetworkingV1alpha3().WorkloadEntries(namespace).Delete(context.TODO(),
name, deleteOptions)
default:
return fmt.Errorf("unsupported type: %v", typ)
}
@@ -852,6 +916,25 @@ var translationMap = map[config.GroupVersionKind]func(r
runtime.Object) config.C
Spec: obj,
}
},
+ gvk.ServiceEntry: func(r runtime.Object) config.Config {
+ obj :=
r.(*apigithubcomapachedubbokubernetesapinetworkingv1alpha3.ServiceEntry)
+ return config.Config{
+ Meta: config.Meta{
+ GroupVersionKind: gvk.ServiceEntry,
+ 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,
+ }
+ },
gvk.StatefulSet: func(r runtime.Object) config.Config {
obj := r.(*k8sioapiappsv1.StatefulSet)
return config.Config{
@@ -907,4 +990,23 @@ var translationMap = map[config.GroupVersionKind]func(r
runtime.Object) config.C
Spec: obj,
}
},
+ gvk.WorkloadEntry: func(r runtime.Object) config.Config {
+ obj :=
r.(*apigithubcomapachedubbokubernetesapinetworkingv1alpha3.WorkloadEntry)
+ return config.Config{
+ Meta: config.Meta{
+ GroupVersionKind: gvk.WorkloadEntry,
+ 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,
+ }
+ },
}
diff --git a/dubbod/discovery/pkg/serviceregistry/aggregate/controller.go
b/dubbod/discovery/pkg/serviceregistry/aggregate/controller.go
index d4fb226a..1a1765b0 100644
--- a/dubbod/discovery/pkg/serviceregistry/aggregate/controller.go
+++ b/dubbod/discovery/pkg/serviceregistry/aggregate/controller.go
@@ -100,8 +100,16 @@ func (c *Controller) Services() []*model.Service {
for _, r := range c.GetRegistries() {
svcs := r.Services()
if r.Provider() != provider.Kubernetes {
- index += len(svcs)
- services = append(services, svcs...)
+ for _, s := range svcs {
+ if previous, found := smap[s.Hostname]; found
&& r.Provider() == provider.External {
+ services[previous] =
services[previous].DeepCopy()
+ decorateService(services[previous], s)
+ continue
+ }
+ smap[s.Hostname] = index
+ index++
+ services = append(services, s)
+ }
} else {
for _, s := range svcs {
previous, ok := smap[s.Hostname]
@@ -138,7 +146,13 @@ func (c *Controller) GetService(hostname host.Name)
*model.Service {
continue
}
if r.Provider() != provider.Kubernetes {
- return service
+ if out == nil {
+ return service
+ }
+ if r.Provider() == provider.External {
+ decorateService(out, service)
+ }
+ return out
}
if out == nil {
out = service.DeepCopy()
@@ -150,6 +164,20 @@ func (c *Controller) GetService(hostname host.Name)
*model.Service {
return out
}
+func decorateService(dst, src *model.Service) {
+ accounts := make(map[string]struct{},
len(dst.ServiceAccounts)+len(src.ServiceAccounts))
+ for _, account := range dst.ServiceAccounts {
+ accounts[account] = struct{}{}
+ }
+ for _, account := range src.ServiceAccounts {
+ if _, found := accounts[account]; found {
+ continue
+ }
+ dst.ServiceAccounts = append(dst.ServiceAccounts, account)
+ accounts[account] = struct{}{}
+ }
+}
+
func (c *Controller) GetRegistries() []serviceregistry.Instance {
c.storeLock.RLock()
defer c.storeLock.RUnlock()
diff --git a/dubbod/discovery/pkg/serviceregistry/aggregate/controller_test.go
b/dubbod/discovery/pkg/serviceregistry/aggregate/controller_test.go
new file mode 100644
index 00000000..4ed65b7e
--- /dev/null
+++ b/dubbod/discovery/pkg/serviceregistry/aggregate/controller_test.go
@@ -0,0 +1,69 @@
+//
+// 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 aggregate
+
+import (
+ "testing"
+
+ "github.com/apache/dubbo-kubernetes/dubbod/discovery/pkg/model"
+
"github.com/apache/dubbo-kubernetes/dubbod/discovery/pkg/serviceregistry/provider"
+ "github.com/apache/dubbo-kubernetes/pkg/cluster"
+ "github.com/apache/dubbo-kubernetes/pkg/config/host"
+)
+
+func TestServiceEntryDecoratesKubernetesService(t *testing.T) {
+ hostname := host.Name("reviews.bookinfo.svc.cluster.local")
+ kubeService := &model.Service{Hostname: hostname, ServiceAccounts:
[]string{"spiffe://cluster.local/ns/bookinfo/sa/default"}}
+ externalService := &model.Service{Hostname: hostname, ServiceAccounts:
[]string{
+ "spiffe://cluster.local/ns/bookinfo/sa/default",
+ "spiffe://cluster.local/ns/bookinfo/sa/reviews",
+ }}
+ controller := NewController(Options{})
+ controller.addRegistry(&staticRegistry{provider: provider.Kubernetes,
services: []*model.Service{kubeService}}, nil)
+ controller.addRegistry(&staticRegistry{provider: provider.External,
services: []*model.Service{externalService}}, nil)
+
+ services := controller.Services()
+ if len(services) != 1 {
+ t.Fatalf("got %d services, want one merged service",
len(services))
+ }
+ if got := len(services[0].ServiceAccounts); got != 2 {
+ t.Fatalf("got %d service accounts, want 2", got)
+ }
+ if got := controller.GetService(hostname); got == nil ||
len(got.ServiceAccounts) != 2 {
+ t.Fatalf("GetService() = %#v, want decorated Kubernetes
service", got)
+ }
+}
+
+type staticRegistry struct {
+ provider provider.ID
+ services []*model.Service
+}
+
+func (*staticRegistry) Run(stop <-chan struct{})
{ <-stop }
+func (*staticRegistry) HasSynced() bool
{ return true }
+func (s *staticRegistry) Services() []*model.Service
{ return s.services }
+func (s *staticRegistry) Provider() provider.ID
{ return s.provider }
+func (*staticRegistry) Cluster() cluster.ID
{ return "cluster-1" }
+func (*staticRegistry) GetProxyServiceTargets(*model.Proxy)
[]model.ServiceTarget { return nil }
+func (s *staticRegistry) GetService(name host.Name) *model.Service {
+ for _, service := range s.services {
+ if service.Hostname == name {
+ return service
+ }
+ }
+ return nil
+}
diff --git a/dubbod/discovery/pkg/serviceregistry/provider/provider.go
b/dubbod/discovery/pkg/serviceregistry/provider/provider.go
index f484271a..10be63d0 100644
--- a/dubbod/discovery/pkg/serviceregistry/provider/provider.go
+++ b/dubbod/discovery/pkg/serviceregistry/provider/provider.go
@@ -22,6 +22,8 @@ type ID string
const (
// Kubernetes is a service registry backed by k8s API server
Kubernetes ID = "Kubernetes"
+ // External is a service registry backed by declarative ServiceEntry
resources.
+ External ID = "External"
)
func (id ID) String() string {
diff --git a/dubbod/discovery/pkg/serviceregistry/serviceentry/controller.go
b/dubbod/discovery/pkg/serviceregistry/serviceentry/controller.go
new file mode 100644
index 00000000..cd454339
--- /dev/null
+++ b/dubbod/discovery/pkg/serviceregistry/serviceentry/controller.go
@@ -0,0 +1,311 @@
+//
+// 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 serviceentry
+
+import (
+ "sort"
+ "strconv"
+ "sync"
+
+ "github.com/apache/dubbo-kubernetes/dubbod/discovery/pkg/model"
+
"github.com/apache/dubbo-kubernetes/dubbod/discovery/pkg/serviceregistry/provider"
+ "github.com/apache/dubbo-kubernetes/pkg/cluster"
+ "github.com/apache/dubbo-kubernetes/pkg/config"
+ "github.com/apache/dubbo-kubernetes/pkg/config/constants"
+ "github.com/apache/dubbo-kubernetes/pkg/config/host"
+ "github.com/apache/dubbo-kubernetes/pkg/config/labels"
+ "github.com/apache/dubbo-kubernetes/pkg/config/protocol"
+ "github.com/apache/dubbo-kubernetes/pkg/config/schema/gvk"
+ "github.com/apache/dubbo-kubernetes/pkg/config/visibility"
+ "github.com/apache/dubbo-kubernetes/pkg/slices"
+ "github.com/apache/dubbo-kubernetes/pkg/util/sets"
+ networking "github.com/kdubbo/api/networking/v1alpha3"
+)
+
+type Options struct {
+ ConfigController model.ConfigStoreController
+ XDSUpdater model.XDSUpdater
+ ClusterID cluster.ID
+}
+
+type Controller struct {
+ mu sync.RWMutex
+ reconcileMu sync.Mutex
+ configController model.ConfigStoreController
+ xdsUpdater model.XDSUpdater
+ clusterID cluster.ID
+ services map[host.Name]*model.Service
+ endpoints map[string][]*model.DubboEndpoint
+}
+
+func NewController(opts Options) *Controller {
+ c := &Controller{
+ configController: opts.ConfigController,
+ xdsUpdater: opts.XDSUpdater,
+ clusterID: opts.ClusterID,
+ services: make(map[host.Name]*model.Service),
+ endpoints: make(map[string][]*model.DubboEndpoint),
+ }
+ opts.ConfigController.RegisterEventHandler(gvk.ServiceEntry,
c.onConfigEvent)
+ opts.ConfigController.RegisterEventHandler(gvk.WorkloadEntry,
c.onConfigEvent)
+ return c
+}
+
+func (c *Controller) onConfigEvent(_, _ config.Config, _ model.Event) {
+ c.reconcile()
+}
+
+func (c *Controller) reconcile() {
+ c.reconcileMu.Lock()
+ defer c.reconcileMu.Unlock()
+
+ entries := c.configController.List(gvk.ServiceEntry, model.NamespaceAll)
+ workloads := c.configController.List(gvk.WorkloadEntry,
model.NamespaceAll)
+ sort.Slice(entries, func(i, j int) bool {
+ if
entries[i].CreationTimestamp.Equal(entries[j].CreationTimestamp) {
+ if entries[i].Namespace == entries[j].Namespace {
+ return entries[i].Name < entries[j].Name
+ }
+ return entries[i].Namespace < entries[j].Namespace
+ }
+ return
entries[i].CreationTimestamp.Before(entries[j].CreationTimestamp)
+ })
+
+ services := make(map[host.Name]*model.Service)
+ endpoints := make(map[string][]*model.DubboEndpoint)
+ for _, cfg := range entries {
+ entry, ok := cfg.Spec.(*networking.ServiceEntry)
+ if !ok {
+ continue
+ }
+ selected := selectWorkloads(cfg.Namespace, entry, workloads)
+ for _, hostname := range entry.GetHosts() {
+ h := host.Name(hostname)
+ if _, found := services[h]; found {
+ continue
+ }
+ svc := c.convertService(cfg, entry, h)
+ services[h] = svc
+ endpoints[svc.Key()] = buildEndpoints(cfg, entry, svc,
selected)
+ }
+ }
+
+ c.mu.Lock()
+ previous := c.services
+ previousEndpoints := c.endpoints
+ c.services = services
+ c.endpoints = endpoints
+ c.mu.Unlock()
+
+ if c.xdsUpdater == nil {
+ return
+ }
+ shard := model.ShardKeyFromRegistry(c)
+ for hostname, svc := range services {
+ oldService := previous[hostname]
+ if oldService == nil {
+ c.xdsUpdater.ServiceUpdate(shard, string(hostname),
svc.Attributes.Namespace, model.EventAdd)
+ c.xdsUpdater.EDSUpdate(shard, string(hostname),
svc.Attributes.Namespace, endpoints[svc.Key()])
+ continue
+ }
+ if oldService.Attributes.Namespace != svc.Attributes.Namespace {
+ c.xdsUpdater.ServiceUpdate(shard, string(hostname),
oldService.Attributes.Namespace, model.EventDelete)
+ c.xdsUpdater.EDSUpdate(shard, string(hostname),
oldService.Attributes.Namespace, nil)
+ c.xdsUpdater.ServiceUpdate(shard, string(hostname),
svc.Attributes.Namespace, model.EventAdd)
+ c.xdsUpdater.EDSUpdate(shard, string(hostname),
svc.Attributes.Namespace, endpoints[svc.Key()])
+ continue
+ }
+ if !oldService.Equals(svc) {
+ c.xdsUpdater.ServiceUpdate(shard, string(hostname),
svc.Attributes.Namespace, model.EventUpdate)
+ }
+ if !slices.EqualFunc(previousEndpoints[oldService.Key()],
endpoints[svc.Key()], func(a, b *model.DubboEndpoint) bool {
+ return a.Equals(b)
+ }) {
+ c.xdsUpdater.EDSUpdate(shard, string(hostname),
svc.Attributes.Namespace, endpoints[svc.Key()])
+ }
+ }
+ for hostname, svc := range previous {
+ if _, found := services[hostname]; found {
+ continue
+ }
+ c.xdsUpdater.ServiceUpdate(shard, string(hostname),
svc.Attributes.Namespace, model.EventDelete)
+ c.xdsUpdater.EDSUpdate(shard, string(hostname),
svc.Attributes.Namespace, nil)
+ }
+}
+
+func (c *Controller) convertService(cfg config.Config, entry
*networking.ServiceEntry, hostname host.Name) *model.Service {
+ addresses := append([]string(nil), entry.GetAddresses()...)
+ defaultAddress := constants.UnspecifiedIP
+ if len(addresses) > 0 {
+ defaultAddress = addresses[0]
+ } else {
+ addresses = []string{defaultAddress}
+ }
+ ports := make(model.PortList, 0, len(entry.GetPorts()))
+ for _, port := range entry.GetPorts() {
+ ports = append(ports, &model.Port{Name: port.GetName(), Port:
int(port.GetNumber()), Protocol: protocol.Parse(port.GetProtocol())})
+ }
+ exportTo := sets.New[visibility.Instance]()
+ for _, namespace := range entry.GetExportTo() {
+ exportTo.Insert(visibility.Instance(namespace))
+ }
+ service := &model.Service{
+ Hostname: hostname,
+ Ports: ports,
+ ServiceAccounts: append([]string(nil),
entry.GetSubjectAltNames()...),
+ ClusterVIPs: model.AddressMap{Addresses:
map[cluster.ID][]string{
+ c.clusterID: addresses,
+ }},
+ CreationTime: cfg.CreationTimestamp,
+ DefaultAddress: defaultAddress,
+ ResourceVersion: cfg.ResourceVersion,
+ Resolution: convertResolution(entry.GetResolution()),
+ MeshExternal: entry.GetLocation() ==
networking.ServiceEntry_MESH_EXTERNAL,
+ Attributes: model.ServiceAttributes{
+ Name: cfg.Name,
+ Namespace: cfg.Namespace,
+ ExportTo: exportTo,
+ ServiceRegistry: provider.External,
+ },
+ }
+ service.Attributes.ObjectName = cfg.Name
+ return service
+}
+
+func convertResolution(resolution networking.ServiceEntry_Resolution)
model.Resolution {
+ switch resolution {
+ case networking.ServiceEntry_STATIC:
+ return model.ClientSideLB
+ case networking.ServiceEntry_DNS:
+ return model.DNSLB
+ case networking.ServiceEntry_DNS_ROUND_ROBIN:
+ return model.DNSRoundRobinLB
+ default:
+ return model.Passthrough
+ }
+}
+
+func selectWorkloads(namespace string, entry *networking.ServiceEntry, configs
[]config.Config) []namedWorkload {
+ selector := entry.GetWorkloadSelector()
+ if selector == nil {
+ out := make([]namedWorkload, 0, len(entry.GetEndpoints()))
+ for i, workload := range entry.GetEndpoints() {
+ out = append(out, namedWorkload{name: entryName(i),
workload: workload})
+ }
+ return out
+ }
+ wanted := labels.Instance(selector.GetMatchLabels())
+ out := make([]namedWorkload, 0)
+ for _, cfg := range configs {
+ if cfg.Namespace != namespace {
+ continue
+ }
+ workload, ok := cfg.Spec.(*networking.WorkloadEntry)
+ if ok && wanted.Match(labels.Instance(workload.GetLabels())) {
+ out = append(out, namedWorkload{name: cfg.Name,
workload: workload})
+ }
+ }
+ sort.Slice(out, func(i, j int) bool { return out[i].name < out[j].name
})
+ return out
+}
+
+type namedWorkload struct {
+ name string
+ workload *networking.WorkloadEntry
+}
+
+func entryName(index int) string {
+ return "inline-" + strconv.Itoa(index)
+}
+
+func buildEndpoints(cfg config.Config, entry *networking.ServiceEntry, svc
*model.Service, workloads []namedWorkload) []*model.DubboEndpoint {
+ if len(workloads) == 0 && (entry.GetResolution() ==
networking.ServiceEntry_DNS || entry.GetResolution() ==
networking.ServiceEntry_DNS_ROUND_ROBIN) {
+ workloads = []namedWorkload{{name: cfg.Name, workload:
&networking.WorkloadEntry{Address: string(svc.Hostname)}}}
+ }
+ result := make([]*model.DubboEndpoint, 0,
len(workloads)*len(entry.GetPorts()))
+ for _, named := range workloads {
+ for _, port := range entry.GetPorts() {
+ targetPort := named.workload.GetPorts()[port.GetName()]
+ if targetPort == 0 {
+ targetPort = port.GetTargetPort()
+ }
+ if targetPort == 0 {
+ targetPort = port.GetNumber()
+ }
+ result = append(result, &model.DubboEndpoint{
+ ServiceAccount:
named.workload.GetServiceAccount(),
+ Addresses:
[]string{named.workload.GetAddress()},
+ ServicePortName: port.GetName(),
+ Labels:
labels.Instance(named.workload.GetLabels()),
+ HealthStatus: model.Healthy,
+ EndpointPort: targetPort,
+ WorkloadName: named.name,
+ Namespace: cfg.Namespace,
+ })
+ }
+ }
+ return result
+}
+
+func (c *Controller) Services() []*model.Service {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+ out := make([]*model.Service, 0, len(c.services))
+ for _, svc := range c.services {
+ out = append(out, svc)
+ }
+ sort.Slice(out, func(i, j int) bool { return out[i].Hostname <
out[j].Hostname })
+ return out
+}
+
+func (c *Controller) GetService(hostname host.Name) *model.Service {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+ return c.services[hostname]
+}
+
+func (c *Controller) GetProxyServiceTargets(proxy *model.Proxy)
[]model.ServiceTarget {
+ if proxy == nil {
+ return nil
+ }
+ addresses := sets.New(proxy.IPAddresses...)
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+ var out []model.ServiceTarget
+ for _, svc := range c.services {
+ for _, endpoint := range c.endpoints[svc.Key()] {
+ if !addresses.Contains(endpoint.FirstAddressOrNil()) {
+ continue
+ }
+ for _, port := range svc.Ports {
+ if port.Name == endpoint.ServicePortName {
+ out = append(out,
model.ServiceTarget{Service: svc, Port: model.ServiceInstancePort{ServicePort:
port, TargetPort: endpoint.EndpointPort}})
+ }
+ }
+ }
+ }
+ return out
+}
+
+func (c *Controller) Provider() provider.ID { return provider.External }
+func (c *Controller) Cluster() cluster.ID { return c.clusterID }
+func (c *Controller) HasSynced() bool { return
c.configController.HasSynced() }
+
+func (c *Controller) Run(stop <-chan struct{}) {
+ c.reconcile()
+ <-stop
+}
diff --git
a/dubbod/discovery/pkg/serviceregistry/serviceentry/controller_test.go
b/dubbod/discovery/pkg/serviceregistry/serviceentry/controller_test.go
new file mode 100644
index 00000000..7c822608
--- /dev/null
+++ b/dubbod/discovery/pkg/serviceregistry/serviceentry/controller_test.go
@@ -0,0 +1,197 @@
+//
+// 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 serviceentry
+
+import (
+ "testing"
+
+ "github.com/apache/dubbo-kubernetes/dubbod/discovery/pkg/config/memory"
+ "github.com/apache/dubbo-kubernetes/dubbod/discovery/pkg/model"
+ "github.com/apache/dubbo-kubernetes/pkg/cluster"
+ "github.com/apache/dubbo-kubernetes/pkg/config"
+ "github.com/apache/dubbo-kubernetes/pkg/config/schema/collections"
+ "github.com/apache/dubbo-kubernetes/pkg/config/schema/gvk"
+ networking "github.com/kdubbo/api/networking/v1alpha3"
+ typev1alpha3 "github.com/kdubbo/api/type/v1alpha3"
+)
+
+func TestControllerReconcilesServiceEntryAndWorkloadEntry(t *testing.T) {
+ configs := newFakeConfigController()
+ updater := &fakeXDSUpdater{}
+ controller := NewController(Options{
+ ConfigController: configs,
+ XDSUpdater: updater,
+ ClusterID: cluster.ID("cluster-1"),
+ })
+
+ workload := config.Config{Meta: config.Meta{
+ GroupVersionKind: gvk.WorkloadEntry,
+ Name: "reviews-vm",
+ Namespace: "bookinfo",
+ }, Spec: &networking.WorkloadEntry{
+ Address: "10.0.0.10",
+ Ports: map[string]uint32{"grpc": 16000},
+ Labels: map[string]string{"app": "reviews"},
+ ServiceAccount: "reviews",
+ }}
+ configs.create(t, workload)
+ entry := config.Config{Meta: config.Meta{
+ GroupVersionKind: gvk.ServiceEntry,
+ Name: "reviews-external",
+ Namespace: "bookinfo",
+ }, Spec: &networking.ServiceEntry{
+ Hosts: []string{"reviews.example.com"},
+ Addresses: []string{"240.0.0.1"},
+ Location: networking.ServiceEntry_MESH_INTERNAL,
+ Resolution: networking.ServiceEntry_STATIC,
+ Ports: []*networking.ServicePort{{
+ Name: "grpc", Number: 50051, Protocol: "GRPC",
TargetPort: 15000,
+ }},
+ WorkloadSelector: &typev1alpha3.WorkloadSelector{MatchLabels:
map[string]string{"app": "reviews"}},
+ }}
+ configs.create(t, entry)
+
+ service := controller.GetService("reviews.example.com")
+ if service == nil {
+ t.Fatal("service was not created")
+ }
+ if service.DefaultAddress != "240.0.0.1" || service.Resolution !=
model.ClientSideLB || service.MeshExternal {
+ t.Fatalf("unexpected service: address=%s resolution=%v
external=%v", service.DefaultAddress, service.Resolution, service.MeshExternal)
+ }
+ last := updater.lastEDS(t)
+ if len(last.endpoints) != 1 {
+ t.Fatalf("got %d endpoints, want 1", len(last.endpoints))
+ }
+ endpoint := last.endpoints[0]
+ if endpoint.FirstAddressOrNil() != "10.0.0.10" || endpoint.EndpointPort
!= 16000 || endpoint.ServiceAccount != "reviews" {
+ t.Fatalf("unexpected endpoint: address=%s port=%d
serviceAccount=%s", endpoint.FirstAddressOrNil(), endpoint.EndpointPort,
endpoint.ServiceAccount)
+ }
+
+ workload.Spec.(*networking.WorkloadEntry).Address = "10.0.0.11"
+ configs.update(t, workload)
+ if got := updater.lastEDS(t).endpoints[0].FirstAddressOrNil(); got !=
"10.0.0.11" {
+ t.Fatalf("updated endpoint address = %s, want 10.0.0.11", got)
+ }
+ if got := len(updater.serviceEvents); got != 1 {
+ t.Fatalf("endpoint-only update emitted %d service events, want
1 total", got)
+ }
+ edsUpdates := len(updater.edsEvents)
+ configs.create(t, config.Config{Meta: config.Meta{
+ GroupVersionKind: gvk.WorkloadEntry,
+ Name: "unrelated",
+ Namespace: "bookinfo",
+ }, Spec: &networking.WorkloadEntry{Address: "10.0.0.20", Labels:
map[string]string{"app": "other"}}})
+ if got := len(updater.edsEvents); got != edsUpdates {
+ t.Fatalf("unrelated workload emitted an EDS update: got %d
updates, want %d", got, edsUpdates)
+ }
+
+ configs.delete(t, gvk.ServiceEntry, entry.Name, entry.Namespace)
+ if controller.GetService("reviews.example.com") != nil {
+ t.Fatal("service was not deleted")
+ }
+ if got := updater.serviceEvents[len(updater.serviceEvents)-1].event;
got != model.EventDelete {
+ t.Fatalf("last service event = %v, want delete", got)
+ }
+ if got := len(updater.lastEDS(t).endpoints); got != 0 {
+ t.Fatalf("deleted service has %d endpoints, want 0", got)
+ }
+}
+
+type fakeConfigController struct {
+ model.ConfigStore
+ handlers map[config.GroupVersionKind][]model.EventHandler
+}
+
+func newFakeConfigController() *fakeConfigController {
+ return &fakeConfigController{
+ ConfigStore: memory.Make(collections.Dubbo),
+ handlers:
make(map[config.GroupVersionKind][]model.EventHandler),
+ }
+}
+
+func (f *fakeConfigController) RegisterEventHandler(kind
config.GroupVersionKind, handler model.EventHandler) {
+ f.handlers[kind] = append(f.handlers[kind], handler)
+}
+
+func (f *fakeConfigController) Run(stop <-chan struct{}) { <-stop }
+func (f *fakeConfigController) HasSynced() bool { return true }
+
+func (f *fakeConfigController) create(t *testing.T, cfg config.Config) {
+ t.Helper()
+ if _, err := f.ConfigStore.Create(cfg); err != nil {
+ t.Fatal(err)
+ }
+ current := *f.Get(cfg.GroupVersionKind, cfg.Name, cfg.Namespace)
+ for _, handler := range f.handlers[cfg.GroupVersionKind] {
+ handler(config.Config{}, current, model.EventAdd)
+ }
+}
+
+func (f *fakeConfigController) update(t *testing.T, cfg config.Config) {
+ t.Helper()
+ old := *f.Get(cfg.GroupVersionKind, cfg.Name, cfg.Namespace)
+ cfg.ResourceVersion = ""
+ if _, err := f.ConfigStore.Update(cfg); err != nil {
+ t.Fatal(err)
+ }
+ current := *f.Get(cfg.GroupVersionKind, cfg.Name, cfg.Namespace)
+ for _, handler := range f.handlers[cfg.GroupVersionKind] {
+ handler(old, current, model.EventUpdate)
+ }
+}
+
+func (f *fakeConfigController) delete(t *testing.T, kind
config.GroupVersionKind, name, namespace string) {
+ t.Helper()
+ old := *f.Get(kind, name, namespace)
+ if err := f.ConfigStore.Delete(kind, name, namespace, nil); err != nil {
+ t.Fatal(err)
+ }
+ for _, handler := range f.handlers[kind] {
+ handler(old, config.Config{}, model.EventDelete)
+ }
+}
+
+type serviceEvent struct {
+ event model.Event
+}
+
+type edsEvent struct {
+ endpoints []*model.DubboEndpoint
+}
+
+type fakeXDSUpdater struct {
+ serviceEvents []serviceEvent
+ edsEvents []edsEvent
+}
+
+func (*fakeXDSUpdater) ConfigUpdate(*model.PushRequest) {}
+func (f *fakeXDSUpdater) ServiceUpdate(_ model.ShardKey, _, _ string, event
model.Event) {
+ f.serviceEvents = append(f.serviceEvents, serviceEvent{event: event})
+}
+func (f *fakeXDSUpdater) EDSUpdate(_ model.ShardKey, _, _ string, endpoints
[]*model.DubboEndpoint) {
+ f.edsEvents = append(f.edsEvents, edsEvent{endpoints: endpoints})
+}
+func (*fakeXDSUpdater) EDSCacheUpdate(model.ShardKey, string, string,
[]*model.DubboEndpoint) {}
+func (*fakeXDSUpdater) ProxyUpdate(cluster.ID, string)
{}
+
+func (f *fakeXDSUpdater) lastEDS(t *testing.T) edsEvent {
+ t.Helper()
+ if len(f.edsEvents) == 0 {
+ t.Fatal("no EDS update received")
+ }
+ return f.edsEvents[len(f.edsEvents)-1]
+}
diff --git a/go.mod b/go.mod
index a4b5d7c3..6922ab24 100644
--- a/go.mod
+++ b/go.mod
@@ -53,8 +53,8 @@ require (
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/golang-lru/v2 v2.0.5
github.com/heroku/color v0.0.6
- github.com/kdubbo/api v0.0.0-20260711063406-ff5334e781fd
- github.com/kdubbo/client-go v0.0.0-20260711063741-a336aba3d8e8
+ github.com/kdubbo/api v0.0.0-20260713105721-558a18fe4f33
+ github.com/kdubbo/client-go v0.0.0-20260713105923-9a87feab8d96
github.com/kdubbo/xds-api v0.0.0-20260628124158-b93008db9162
github.com/moby/term v0.5.2
github.com/ory/viper v1.7.5
diff --git a/go.sum b/go.sum
index 21fbb2ac..9c8e1324 100644
--- a/go.sum
+++ b/go.sum
@@ -337,10 +337,10 @@ github.com/json-iterator/go v1.1.12/go.mod
h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
github.com/jtolds/gls v4.20.0+incompatible/go.mod
h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod
h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
-github.com/kdubbo/api v0.0.0-20260711063406-ff5334e781fd
h1:oMbQY61FOOITVLKRdGX0EirWW3nZN26F+cwttj9DmSM=
-github.com/kdubbo/api v0.0.0-20260711063406-ff5334e781fd/go.mod
h1:8BtJiIovg7QCPsCxXcw3gDf922VcvYq5ihOSvj49Rq8=
-github.com/kdubbo/client-go v0.0.0-20260711063741-a336aba3d8e8
h1:mNvM3yB78CVgozJm4wZxIKK//mBJESofhD2Z0eHLoC0=
-github.com/kdubbo/client-go v0.0.0-20260711063741-a336aba3d8e8/go.mod
h1:HDLiCm6am1zZTLGq+tNt7S/lZfpmU/zRdVJyOp0yymU=
+github.com/kdubbo/api v0.0.0-20260713105721-558a18fe4f33
h1://TxoBFL/igcCqokYHOcA5hA30J6LTpFusDYVQx48Nw=
+github.com/kdubbo/api v0.0.0-20260713105721-558a18fe4f33/go.mod
h1:8BtJiIovg7QCPsCxXcw3gDf922VcvYq5ihOSvj49Rq8=
+github.com/kdubbo/client-go v0.0.0-20260713105923-9a87feab8d96
h1:co2FdpetF4dIozVHMatAd2yYPtKBqdpCgSbpdnXEkKw=
+github.com/kdubbo/client-go v0.0.0-20260713105923-9a87feab8d96/go.mod
h1:NUl5DTlsovJaoYDj0SyHi263H+1Z11EXP7qnk6TPKjo=
github.com/kdubbo/xds-api v0.0.0-20260628124158-b93008db9162
h1:8T6FM/7KYiX84f8wMNmWxZ4YMv+uONCkZqTKiM6m/M8=
github.com/kdubbo/xds-api v0.0.0-20260628124158-b93008db9162/go.mod
h1:o2HDUgL1ntaDbWomZ4cD2tt8jBamuG2qRtjXOa1zZ0Q=
github.com/kevinburke/ssh_config v1.2.0
h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
diff --git a/manifests/charts/base/files/crd-all.gen.yaml
b/manifests/charts/base/files/crd-all.gen.yaml
index fda86cd0..422dd049 100644
--- a/manifests/charts/base/files/crd-all.gen.yaml
+++ b/manifests/charts/base/files/crd-all.gen.yaml
@@ -152,6 +152,341 @@ spec:
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
+metadata:
+ annotations:
+ "helm.sh/resource-policy": keep
+ labels:
+ app: dubbo
+ chart: dubbo
+ dubbo: networking
+ heritage: Tiller
+ release: dubbo
+ name: serviceentries.networking.dubbo.apache.org
+spec:
+ group: networking.dubbo.apache.org
+ names:
+ categories:
+ - dubbo
+ - networking
+ kind: ServiceEntry
+ listKind: ServiceEntryList
+ plural: serviceentries
+ shortNames:
+ - se
+ singular: serviceentry
+ scope: Namespaced
+ versions:
+ - additionalPrinterColumns:
+ - description: Service hosts.
+ jsonPath: .spec.hosts
+ name: Hosts
+ type: string
+ - description: Service location.
+ jsonPath: .spec.location
+ name: Location
+ type: string
+ - description: Service resolution.
+ jsonPath: .spec.resolution
+ name: Resolution
+ type: string
+ - description: CreationTimestamp is a timestamp representing the server
time when
+ this object was created.
+ jsonPath: .metadata.creationTimestamp
+ name: Age
+ type: date
+ name: v1alpha3
+ schema:
+ openAPIV3Schema:
+ properties:
+ spec:
+ description: 'Declarative entries for the internal service
registry. See
+ more details at: '
+ properties:
+ addresses:
+ description: Virtual IP addresses associated with the service.
+ items:
+ type: string
+ maxItems: 256
+ type: array
+ endpoints:
+ description: Inline endpoints associated with the service.
+ items:
+ properties:
+ address:
+ description: IP address or fully-qualified DNS name of
the workload.
+ maxLength: 256
+ minLength: 1
+ type: string
+ labels:
+ additionalProperties:
+ type: string
+ description: Labels used by
ServiceEntry.workloadSelector.
+ maxProperties: 256
+ type: object
+ locality:
+ description: Locality in region/zone/subzone form.
+ maxLength: 2048
+ type: string
+ network:
+ description: Network containing the workload.
+ maxLength: 2048
+ type: string
+ ports:
+ additionalProperties:
+ maximum: 4294967295
+ minimum: 0
+ type: integer
+ x-kubernetes-validations:
+ - message: port must be between 1-65535
+ rule: 0 < self && self <= 65535
+ description: Per-service-port endpoint ports.
+ maxProperties: 128
+ type: object
+ serviceAccount:
+ description: Service account used to derive workload
identity.
+ maxLength: 253
+ type: string
+ weight:
+ description: Relative load-balancing weight.
+ maximum: 4294967295
+ minimum: 0
+ type: integer
+ type: object
+ maxItems: 4096
+ type: array
+ exportTo:
+ description: Namespaces to which the service is exported.
+ items:
+ type: string
+ type: array
+ hosts:
+ description: DNS hosts associated with the service.
+ items:
+ type: string
+ maxItems: 256
+ minItems: 1
+ type: array
+ location:
+ description: |2-
+
+
+ Valid Options: MESH_EXTERNAL, MESH_INTERNAL
+ enum:
+ - MESH_EXTERNAL
+ - MESH_INTERNAL
+ type: string
+ ports:
+ description: Ports exposed by the service.
+ items:
+ properties:
+ name:
+ description: Stable name used by WorkloadEntry.ports.
+ maxLength: 256
+ type: string
+ number:
+ maximum: 4294967295
+ minimum: 0
+ type: integer
+ x-kubernetes-validations:
+ - message: port must be between 1-65535
+ rule: 0 < self && self <= 65535
+ protocol:
+ description: Protocol exposed by the port, for example
GRPC,
+ HTTP2, TCP, or TLS.
+ maxLength: 256
+ type: string
+ targetPort:
+ description: Default endpoint port.
+ maximum: 4294967295
+ minimum: 0
+ type: integer
+ required:
+ - number
+ - name
+ type: object
+ maxItems: 256
+ type: array
+ x-kubernetes-list-map-keys:
+ - name
+ x-kubernetes-list-type: map
+ resolution:
+ description: |2-
+
+
+ Valid Options: NONE, STATIC, DNS, DNS_ROUND_ROBIN
+ enum:
+ - NONE
+ - STATIC
+ - DNS
+ - DNS_ROUND_ROBIN
+ type: string
+ subjectAltNames:
+ description: Subject alternative names accepted for the
service.
+ items:
+ type: string
+ type: array
+ workloadSelector:
+ description: Selects WorkloadEntry resources in the same
namespace.
+ properties:
+ matchLabels:
+ additionalProperties:
+ maxLength: 63
+ type: string
+ x-kubernetes-validations:
+ - message: wildcard not allowed in label value match
+ rule: '!self.contains("*")'
+ maxProperties: 4096
+ type: object
+ x-kubernetes-validations:
+ - message: wildcard not allowed in label key match
+ rule: self.all(key, !key.contains("*"))
+ - message: key must not be empty
+ rule: self.all(key, key.size() != 0)
+ type: object
+ required:
+ - hosts
+ type: object
+ x-kubernetes-validations:
+ - message: only one of workloadSelector or endpoints can be set
+ rule: '!(has(self.workloadSelector) && has(self.endpoints))'
+ status:
+ properties:
+ conditions:
+ items:
+ properties:
+ observedGeneration:
+ anyOf:
+ - type: integer
+ - type: string
+ x-kubernetes-int-or-string: true
+ reason:
+ type: string
+ status:
+ type: string
+ type:
+ type: string
+ type: object
+ type: array
+ 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: dubbo
+ chart: dubbo
+ dubbo: networking
+ heritage: Tiller
+ release: dubbo
+ name: workloadentries.networking.dubbo.apache.org
+spec:
+ group: networking.dubbo.apache.org
+ names:
+ categories:
+ - dubbo
+ - networking
+ kind: WorkloadEntry
+ listKind: WorkloadEntryList
+ plural: workloadentries
+ shortNames:
+ - we
+ singular: workloadentry
+ scope: Namespaced
+ versions:
+ - additionalPrinterColumns:
+ - description: Workload address.
+ jsonPath: .spec.address
+ name: Address
+ type: string
+ - description: CreationTimestamp is a timestamp representing the server
time when
+ this object was created.
+ jsonPath: .metadata.creationTimestamp
+ name: Age
+ type: date
+ name: v1alpha3
+ schema:
+ openAPIV3Schema:
+ properties:
+ spec:
+ description: 'Configuration describing a non-Kubernetes workload.
See
+ more details at: '
+ properties:
+ address:
+ description: IP address or fully-qualified DNS name of the
workload.
+ maxLength: 256
+ minLength: 1
+ type: string
+ labels:
+ additionalProperties:
+ type: string
+ description: Labels used by ServiceEntry.workloadSelector.
+ maxProperties: 256
+ type: object
+ locality:
+ description: Locality in region/zone/subzone form.
+ maxLength: 2048
+ type: string
+ network:
+ description: Network containing the workload.
+ maxLength: 2048
+ type: string
+ ports:
+ additionalProperties:
+ maximum: 4294967295
+ minimum: 0
+ type: integer
+ x-kubernetes-validations:
+ - message: port must be between 1-65535
+ rule: 0 < self && self <= 65535
+ description: Per-service-port endpoint ports.
+ maxProperties: 128
+ type: object
+ serviceAccount:
+ description: Service account used to derive workload identity.
+ maxLength: 253
+ type: string
+ weight:
+ description: Relative load-balancing weight.
+ maximum: 4294967295
+ minimum: 0
+ type: integer
+ type: object
+ status:
+ properties:
+ conditions:
+ items:
+ properties:
+ observedGeneration:
+ anyOf:
+ - type: integer
+ - type: string
+ x-kubernetes-int-or-string: true
+ reason:
+ type: string
+ status:
+ type: string
+ type:
+ type: string
+ type: object
+ type: array
+ 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/pkg/config/protocol/instance.go b/pkg/config/protocol/instance.go
index 74733285..95d3cd51 100644
--- a/pkg/config/protocol/instance.go
+++ b/pkg/config/protocol/instance.go
@@ -26,6 +26,12 @@ func (i Instance) String() string {
}
const (
+ // HTTP declares that the port carries HTTP/1.1 traffic.
+ HTTP Instance = "HTTP"
+ // TCP declares that the port carries opaque TCP traffic.
+ TCP Instance = "TCP"
+ // UDP declares that the port carries UDP traffic.
+ UDP Instance = "UDP"
// GRPC declares that the port carries gRPC traffic.
GRPC Instance = "GRPC"
// GRPCWeb declares that the port carries gRPC traffic.
@@ -44,6 +50,12 @@ const (
// Parse from string ignoring case
func Parse(s string) Instance {
switch strings.ToLower(s) {
+ case "http":
+ return HTTP
+ case "tcp":
+ return TCP
+ case "udp":
+ return UDP
case "grpc":
return GRPC
case "grpc-web":
diff --git a/pkg/config/schema/collections/collections.gen.go
b/pkg/config/schema/collections/collections.gen.go
index 63cbcc63..de2c2a0a 100755
--- a/pkg/config/schema/collections/collections.gen.go
+++ b/pkg/config/schema/collections/collections.gen.go
@@ -434,6 +434,21 @@ var (
ValidateProto: validation.EmptyValidate,
}.MustBuild()
+ ServiceEntry = resource.Builder{
+ Identifier: "ServiceEntry",
+ Group: "networking.dubbo.apache.org",
+ Kind: "ServiceEntry",
+ Plural: "serviceentries",
+ Version: "v1alpha3",
+ Proto: "dubbo.networking.v1alpha3.ServiceEntry",
StatusProto: "dubbo.meta.v1alpha1.DubboStatus",
+ ReflectType:
reflect.TypeOf(&githubcomkdubboapinetworkingv1alpha3.ServiceEntry{}).Elem(),
StatusType:
reflect.TypeOf(&githubcomkdubboapimetav1alpha1.DubboStatus{}).Elem(),
+ ProtoPackage: "github.com/kdubbo/api/networking/v1alpha3",
StatusPackage: "github.com/kdubbo/api/meta/v1alpha1",
+ ClusterScoped: false,
+ Synthetic: false,
+ Builtin: false,
+ ValidateProto: validation.ValidateServiceEntry,
+ }.MustBuild()
+
StatefulSet = resource.Builder{
Identifier: "StatefulSet",
Group: "apps",
@@ -479,6 +494,21 @@ var (
ValidateProto: validation.EmptyValidate,
}.MustBuild()
+ WorkloadEntry = resource.Builder{
+ Identifier: "WorkloadEntry",
+ Group: "networking.dubbo.apache.org",
+ Kind: "WorkloadEntry",
+ Plural: "workloadentries",
+ Version: "v1alpha3",
+ Proto: "dubbo.networking.v1alpha3.WorkloadEntry",
StatusProto: "dubbo.meta.v1alpha1.DubboStatus",
+ ReflectType:
reflect.TypeOf(&githubcomkdubboapinetworkingv1alpha3.WorkloadEntry{}).Elem(),
StatusType:
reflect.TypeOf(&githubcomkdubboapimetav1alpha1.DubboStatus{}).Elem(),
+ ProtoPackage: "github.com/kdubbo/api/networking/v1alpha3",
StatusPackage: "github.com/kdubbo/api/meta/v1alpha1",
+ ClusterScoped: false,
+ Synthetic: false,
+ Builtin: false,
+ ValidateProto: validation.ValidateWorkloadEntry,
+ }.MustBuild()
+
// All contains all collections in the system.
All = collection.NewSchemasBuilder().
MustAdd(AuthorizationPolicy).
@@ -507,9 +537,11 @@ var (
MustAdd(Secret).
MustAdd(Service).
MustAdd(ServiceAccount).
+ MustAdd(ServiceEntry).
MustAdd(StatefulSet).
MustAdd(Telemetry).
MustAdd(ValidatingWebhookConfiguration).
+ MustAdd(WorkloadEntry).
Build()
// Kube contains only kubernetes collections.
@@ -545,7 +577,9 @@ var (
MustAdd(CircuitBreakerPolicy).
MustAdd(PeerAuthentication).
MustAdd(RequestAuthentication).
+ MustAdd(ServiceEntry).
MustAdd(Telemetry).
+ MustAdd(WorkloadEntry).
Build()
// dubboGatewayAPI contains only collections used by Dubbo, including
the full Gateway API.
@@ -559,7 +593,9 @@ var (
MustAdd(PeerAuthentication).
MustAdd(ReferenceGrant).
MustAdd(RequestAuthentication).
+ MustAdd(ServiceEntry).
MustAdd(Telemetry).
+ MustAdd(WorkloadEntry).
Build()
// dubboStableGatewayAPI contains only collections used by Dubbo,
including beta+ Gateway API.
@@ -573,6 +609,8 @@ var (
MustAdd(PeerAuthentication).
MustAdd(ReferenceGrant).
MustAdd(RequestAuthentication).
+ MustAdd(ServiceEntry).
MustAdd(Telemetry).
+ MustAdd(WorkloadEntry).
Build()
)
diff --git a/pkg/config/schema/gvk/resources.gen.go
b/pkg/config/schema/gvk/resources.gen.go
index b8410c53..8cd44e99 100755
--- a/pkg/config/schema/gvk/resources.gen.go
+++ b/pkg/config/schema/gvk/resources.gen.go
@@ -41,9 +41,11 @@ var (
Secret = config.GroupVersionKind{Group: "",
Version: "v1", Kind: "Secret"}
Service = config.GroupVersionKind{Group: "",
Version: "v1", Kind: "Service"}
ServiceAccount = config.GroupVersionKind{Group: "",
Version: "v1", Kind: "ServiceAccount"}
+ ServiceEntry = config.GroupVersionKind{Group:
"networking.dubbo.apache.org", Version: "v1alpha3", Kind: "ServiceEntry"}
StatefulSet = config.GroupVersionKind{Group: "apps",
Version: "v1", Kind: "StatefulSet"}
Telemetry = config.GroupVersionKind{Group:
"telemetry.dubbo.apache.org", Version: "v1alpha1", Kind: "Telemetry"}
ValidatingWebhookConfiguration = config.GroupVersionKind{Group:
"admissionregistration.k8s.io", Version: "v1", Kind:
"ValidatingWebhookConfiguration"}
+ WorkloadEntry = config.GroupVersionKind{Group:
"networking.dubbo.apache.org", Version: "v1alpha3", Kind: "WorkloadEntry"}
)
// ToGVR converts a GVK to a GVR.
@@ -111,12 +113,16 @@ func ToGVR(g config.GroupVersionKind)
(schema.GroupVersionResource, bool) {
return gvr.Service, true
case ServiceAccount:
return gvr.ServiceAccount, true
+ case ServiceEntry:
+ return gvr.ServiceEntry, true
case StatefulSet:
return gvr.StatefulSet, true
case Telemetry:
return gvr.Telemetry, true
case ValidatingWebhookConfiguration:
return gvr.ValidatingWebhookConfiguration, true
+ case WorkloadEntry:
+ return gvr.WorkloadEntry, true
}
return schema.GroupVersionResource{}, false
@@ -176,12 +182,16 @@ func MustToKind(g config.GroupVersionKind) kind.Kind {
return kind.Service
case ServiceAccount:
return kind.ServiceAccount
+ case ServiceEntry:
+ return kind.ServiceEntry
case StatefulSet:
return kind.StatefulSet
case Telemetry:
return kind.Telemetry
case ValidatingWebhookConfiguration:
return kind.ValidatingWebhookConfiguration
+ case WorkloadEntry:
+ return kind.WorkloadEntry
}
panic("unknown kind: " + g.String())
@@ -252,12 +262,16 @@ func FromGVR(g schema.GroupVersionResource)
(config.GroupVersionKind, bool) {
return Service, true
case gvr.ServiceAccount:
return ServiceAccount, true
+ case gvr.ServiceEntry:
+ return ServiceEntry, true
case gvr.StatefulSet:
return StatefulSet, true
case gvr.Telemetry:
return Telemetry, true
case gvr.ValidatingWebhookConfiguration:
return ValidatingWebhookConfiguration, true
+ case gvr.WorkloadEntry:
+ return WorkloadEntry, true
}
return config.GroupVersionKind{}, false
diff --git a/pkg/config/schema/gvr/resources.gen.go
b/pkg/config/schema/gvr/resources.gen.go
index b75932c4..62f90f2a 100755
--- a/pkg/config/schema/gvr/resources.gen.go
+++ b/pkg/config/schema/gvr/resources.gen.go
@@ -36,9 +36,11 @@ var (
Secret = schema.GroupVersionResource{Group: "",
Version: "v1", Resource: "secrets"}
Service = schema.GroupVersionResource{Group: "",
Version: "v1", Resource: "services"}
ServiceAccount = schema.GroupVersionResource{Group: "",
Version: "v1", Resource: "serviceaccounts"}
+ ServiceEntry = schema.GroupVersionResource{Group:
"networking.dubbo.apache.org", Version: "v1alpha3", Resource: "serviceentries"}
StatefulSet = schema.GroupVersionResource{Group:
"apps", Version: "v1", Resource: "statefulsets"}
Telemetry = schema.GroupVersionResource{Group:
"telemetry.dubbo.apache.org", Version: "v1alpha1", Resource: "telemetries"}
ValidatingWebhookConfiguration = schema.GroupVersionResource{Group:
"admissionregistration.k8s.io", Version: "v1", Resource:
"validatingwebhookconfigurations"}
+ WorkloadEntry = schema.GroupVersionResource{Group:
"networking.dubbo.apache.org", Version: "v1alpha3", Resource: "workloadentries"}
)
func IsClusterScoped(g schema.GroupVersionResource) bool {
@@ -103,12 +105,16 @@ func IsClusterScoped(g schema.GroupVersionResource) bool {
return false
case ServiceAccount:
return false
+ case ServiceEntry:
+ return false
case StatefulSet:
return false
case Telemetry:
return false
case ValidatingWebhookConfiguration:
return true
+ case WorkloadEntry:
+ return false
}
// shouldn't happen
return false
diff --git a/pkg/config/schema/kind/resources.gen.go
b/pkg/config/schema/kind/resources.gen.go
index 77fe9b98..97193ecb 100755
--- a/pkg/config/schema/kind/resources.gen.go
+++ b/pkg/config/schema/kind/resources.gen.go
@@ -32,9 +32,11 @@ const (
Secret
Service
ServiceAccount
+ ServiceEntry
StatefulSet
Telemetry
ValidatingWebhookConfiguration
+ WorkloadEntry
)
func (k Kind) String() string {
@@ -95,12 +97,16 @@ func (k Kind) String() string {
return "Service"
case ServiceAccount:
return "ServiceAccount"
+ case ServiceEntry:
+ return "ServiceEntry"
case StatefulSet:
return "StatefulSet"
case Telemetry:
return "Telemetry"
case ValidatingWebhookConfiguration:
return "ValidatingWebhookConfiguration"
+ case WorkloadEntry:
+ return "WorkloadEntry"
default:
return "Unknown"
}
@@ -164,12 +170,16 @@ func FromString(s string) Kind {
return Service
case "ServiceAccount":
return ServiceAccount
+ case "ServiceEntry":
+ return ServiceEntry
case "StatefulSet":
return StatefulSet
case "Telemetry":
return Telemetry
case "ValidatingWebhookConfiguration":
return ValidatingWebhookConfiguration
+ case "WorkloadEntry":
+ return WorkloadEntry
default:
return Unknown
}
diff --git a/pkg/config/schema/kubeclient/resources.gen.go
b/pkg/config/schema/kubeclient/resources.gen.go
index 2a88d03c..e63b7a7c 100755
--- a/pkg/config/schema/kubeclient/resources.gen.go
+++ b/pkg/config/schema/kubeclient/resources.gen.go
@@ -84,12 +84,16 @@ func GetWriteClient[T runtime.Object](c ClientGetter,
namespace string) ktypes.W
return
c.Kube().CoreV1().Services(namespace).(ktypes.WriteAPI[T])
case *k8sioapicorev1.ServiceAccount:
return
c.Kube().CoreV1().ServiceAccounts(namespace).(ktypes.WriteAPI[T])
+ case
*apigithubcomapachedubbokubernetesapinetworkingv1alpha3.ServiceEntry:
+ return
c.Dubbo().NetworkingV1alpha3().ServiceEntries(namespace).(ktypes.WriteAPI[T])
case *k8sioapiappsv1.StatefulSet:
return
c.Kube().AppsV1().StatefulSets(namespace).(ktypes.WriteAPI[T])
case *apigithubcomapachedubbokubernetesapitelemetryv1alpha1.Telemetry:
return
c.Dubbo().TelemetryV1alpha1().Telemetries(namespace).(ktypes.WriteAPI[T])
case *k8sioapiadmissionregistrationv1.ValidatingWebhookConfiguration:
return
c.Kube().AdmissionregistrationV1().ValidatingWebhookConfigurations().(ktypes.WriteAPI[T])
+ case
*apigithubcomapachedubbokubernetesapinetworkingv1alpha3.WorkloadEntry:
+ return
c.Dubbo().NetworkingV1alpha3().WorkloadEntries(namespace).(ktypes.WriteAPI[T])
default:
panic(fmt.Sprintf("Unknown type %T", ptr.Empty[T]()))
}
@@ -147,12 +151,16 @@ func GetClient[T, TL runtime.Object](c ClientGetter,
namespace string) ktypes.Re
return
c.Kube().CoreV1().Services(namespace).(ktypes.ReadWriteAPI[T, TL])
case *k8sioapicorev1.ServiceAccount:
return
c.Kube().CoreV1().ServiceAccounts(namespace).(ktypes.ReadWriteAPI[T, TL])
+ case
*apigithubcomapachedubbokubernetesapinetworkingv1alpha3.ServiceEntry:
+ return
c.Dubbo().NetworkingV1alpha3().ServiceEntries(namespace).(ktypes.ReadWriteAPI[T,
TL])
case *k8sioapiappsv1.StatefulSet:
return
c.Kube().AppsV1().StatefulSets(namespace).(ktypes.ReadWriteAPI[T, TL])
case *apigithubcomapachedubbokubernetesapitelemetryv1alpha1.Telemetry:
return
c.Dubbo().TelemetryV1alpha1().Telemetries(namespace).(ktypes.ReadWriteAPI[T,
TL])
case *k8sioapiadmissionregistrationv1.ValidatingWebhookConfiguration:
return
c.Kube().AdmissionregistrationV1().ValidatingWebhookConfigurations().(ktypes.ReadWriteAPI[T,
TL])
+ case
*apigithubcomapachedubbokubernetesapinetworkingv1alpha3.WorkloadEntry:
+ return
c.Dubbo().NetworkingV1alpha3().WorkloadEntries(namespace).(ktypes.ReadWriteAPI[T,
TL])
default:
panic(fmt.Sprintf("Unknown type %T", ptr.Empty[T]()))
}
@@ -210,12 +218,16 @@ func gvrToObject(g schema.GroupVersionResource)
runtime.Object {
return &k8sioapicorev1.Service{}
case gvr.ServiceAccount:
return &k8sioapicorev1.ServiceAccount{}
+ case gvr.ServiceEntry:
+ return
&apigithubcomapachedubbokubernetesapinetworkingv1alpha3.ServiceEntry{}
case gvr.StatefulSet:
return &k8sioapiappsv1.StatefulSet{}
case gvr.Telemetry:
return
&apigithubcomapachedubbokubernetesapitelemetryv1alpha1.Telemetry{}
case gvr.ValidatingWebhookConfiguration:
return
&k8sioapiadmissionregistrationv1.ValidatingWebhookConfiguration{}
+ case gvr.WorkloadEntry:
+ return
&apigithubcomapachedubbokubernetesapinetworkingv1alpha3.WorkloadEntry{}
default:
panic(fmt.Sprintf("Unknown type %v", g))
}
@@ -401,6 +413,13 @@ func getInformerFiltered(c ClientGetter, opts
ktypes.InformerOptions, g schema.G
w = func(options metav1.ListOptions) (watch.Interface, error) {
return
c.Kube().CoreV1().ServiceAccounts(opts.Namespace).Watch(context.Background(),
options)
}
+ case gvr.ServiceEntry:
+ l = func(options metav1.ListOptions) (runtime.Object, error) {
+ return
c.Dubbo().NetworkingV1alpha3().ServiceEntries(opts.Namespace).List(context.Background(),
options)
+ }
+ w = func(options metav1.ListOptions) (watch.Interface, error) {
+ return
c.Dubbo().NetworkingV1alpha3().ServiceEntries(opts.Namespace).Watch(context.Background(),
options)
+ }
case gvr.StatefulSet:
l = func(options metav1.ListOptions) (runtime.Object, error) {
return
c.Kube().AppsV1().StatefulSets(opts.Namespace).List(context.Background(),
options)
@@ -422,6 +441,13 @@ func getInformerFiltered(c ClientGetter, opts
ktypes.InformerOptions, g schema.G
w = func(options metav1.ListOptions) (watch.Interface, error) {
return
c.Kube().AdmissionregistrationV1().ValidatingWebhookConfigurations().Watch(context.Background(),
options)
}
+ case gvr.WorkloadEntry:
+ l = func(options metav1.ListOptions) (runtime.Object, error) {
+ return
c.Dubbo().NetworkingV1alpha3().WorkloadEntries(opts.Namespace).List(context.Background(),
options)
+ }
+ w = func(options metav1.ListOptions) (watch.Interface, error) {
+ return
c.Dubbo().NetworkingV1alpha3().WorkloadEntries(opts.Namespace).Watch(context.Background(),
options)
+ }
default:
panic(fmt.Sprintf("Unknown type %v", g))
}
diff --git a/pkg/config/schema/kubetypes/resources.gen.go
b/pkg/config/schema/kubetypes/resources.gen.go
index a395b2f8..864ee1ad 100755
--- a/pkg/config/schema/kubetypes/resources.gen.go
+++ b/pkg/config/schema/kubetypes/resources.gen.go
@@ -86,6 +86,10 @@ func getGvk(obj any) (config.GroupVersionKind, bool) {
return gvk.Service, true
case *k8sioapicorev1.ServiceAccount:
return gvk.ServiceAccount, true
+ case *githubcomkdubboapinetworkingv1alpha3.ServiceEntry:
+ return gvk.ServiceEntry, true
+ case
*apigithubcomapachedubbokubernetesapinetworkingv1alpha3.ServiceEntry:
+ return gvk.ServiceEntry, true
case *k8sioapiappsv1.StatefulSet:
return gvk.StatefulSet, true
case *githubcomkdubboapitelemetryv1alpha1.Telemetry:
@@ -94,6 +98,10 @@ func getGvk(obj any) (config.GroupVersionKind, bool) {
return gvk.Telemetry, true
case *k8sioapiadmissionregistrationv1.ValidatingWebhookConfiguration:
return gvk.ValidatingWebhookConfiguration, true
+ case *githubcomkdubboapinetworkingv1alpha3.WorkloadEntry:
+ return gvk.WorkloadEntry, true
+ case
*apigithubcomapachedubbokubernetesapinetworkingv1alpha3.WorkloadEntry:
+ return gvk.WorkloadEntry, true
default:
return config.GroupVersionKind{}, false
}
diff --git a/pkg/config/schema/metadata.yaml b/pkg/config/schema/metadata.yaml
index cb22e54b..68d53812 100644
--- a/pkg/config/schema/metadata.yaml
+++ b/pkg/config/schema/metadata.yaml
@@ -230,6 +230,24 @@ resources:
statusProto: "dubbo.meta.v1alpha1.DubboStatus"
statusProtoPackage: "github.com/kdubbo/api/meta/v1alpha1"
+ - kind: ServiceEntry
+ plural: "serviceentries"
+ group: "networking.dubbo.apache.org"
+ version: "v1alpha3"
+ proto: "dubbo.networking.v1alpha3.ServiceEntry"
+ protoPackage: "github.com/kdubbo/api/networking/v1alpha3"
+ statusProto: "dubbo.meta.v1alpha1.DubboStatus"
+ statusProtoPackage: "github.com/kdubbo/api/meta/v1alpha1"
+
+ - kind: WorkloadEntry
+ plural: "workloadentries"
+ group: "networking.dubbo.apache.org"
+ version: "v1alpha3"
+ proto: "dubbo.networking.v1alpha3.WorkloadEntry"
+ protoPackage: "github.com/kdubbo/api/networking/v1alpha3"
+ statusProto: "dubbo.meta.v1alpha1.DubboStatus"
+ statusProtoPackage: "github.com/kdubbo/api/meta/v1alpha1"
+
- kind: "MeshConfig"
plural: "meshconfigs"
group: ""
diff --git a/pkg/config/validation/validators.go
b/pkg/config/validation/validators.go
index fdb93ebd..0b4fa91f 100644
--- a/pkg/config/validation/validators.go
+++ b/pkg/config/validation/validators.go
@@ -19,13 +19,18 @@ package validation
import (
"fmt"
"math"
+ "net"
"strings"
"github.com/apache/dubbo-kubernetes/pkg/config"
"github.com/apache/dubbo-kubernetes/pkg/config/constants"
+ "github.com/apache/dubbo-kubernetes/pkg/config/labels"
+ "github.com/apache/dubbo-kubernetes/pkg/config/protocol"
+ "github.com/apache/dubbo-kubernetes/pkg/config/visibility"
networking "github.com/kdubbo/api/networking/v1alpha3"
security "github.com/kdubbo/api/security/v1alpha3"
telemetry "github.com/kdubbo/api/telemetry/v1alpha1"
+ kvalidation "k8s.io/apimachinery/pkg/util/validation"
)
// ValidateAuthorizationPolicy checks that an AuthorizationPolicy is
well-formed.
@@ -208,6 +213,125 @@ var ValidateCircuitBreakerPolicy = validateFunc(
return v.Unwrap()
})
+// ValidateServiceEntry checks that a ServiceEntry can be converted into
services and endpoints.
+var ValidateServiceEntry = RegisterValidateFunc("ValidateServiceEntry",
func(cfg config.Config) (Warning, error) {
+ spec, ok := cfg.Spec.(*networking.ServiceEntry)
+ if !ok {
+ return nil, fmt.Errorf("cannot cast to ServiceEntry")
+ }
+ v := Validation{}
+ if len(spec.GetHosts()) == 0 {
+ v = appendValidation(v, fmt.Errorf("hosts must not be empty"))
+ }
+ for i, hostname := range spec.GetHosts() {
+ v = appendValidation(v,
validateServiceEntryHost(fmt.Sprintf("hosts[%d]", i), hostname))
+ }
+ for i, address := range spec.GetAddresses() {
+ if net.ParseIP(address) == nil {
+ if _, _, err := net.ParseCIDR(address); err != nil {
+ v = appendValidation(v,
fmt.Errorf("addresses[%d] %q must be an IP address or CIDR", i, address))
+ }
+ }
+ }
+ if len(spec.GetPorts()) == 0 {
+ v = appendValidation(v, fmt.Errorf("ports must not be empty"))
+ }
+ names := make(map[string]struct{}, len(spec.GetPorts()))
+ numbers := make(map[uint32]struct{}, len(spec.GetPorts()))
+ for i, port := range spec.GetPorts() {
+ if port == nil {
+ v = appendValidation(v, fmt.Errorf("ports[%d] must not
be null", i))
+ continue
+ }
+ if port.GetName() == "" {
+ v = appendValidation(v, fmt.Errorf("ports[%d].name must
not be empty", i))
+ } else if _, found := names[port.GetName()]; found {
+ v = appendValidation(v, fmt.Errorf("ports[%d].name %q
is duplicated", i, port.GetName()))
+ }
+ names[port.GetName()] = struct{}{}
+ if port.GetNumber() == 0 || port.GetNumber() > 65535 {
+ v = appendValidation(v, fmt.Errorf("ports[%d].number
must be between 1 and 65535", i))
+ } else if _, found := numbers[port.GetNumber()]; found {
+ v = appendValidation(v, fmt.Errorf("ports[%d].number %d
is duplicated", i, port.GetNumber()))
+ }
+ numbers[port.GetNumber()] = struct{}{}
+ if port.GetTargetPort() > 65535 {
+ v = appendValidation(v,
fmt.Errorf("ports[%d].targetPort must be between 1 and 65535 when set", i))
+ }
+ if protocol.Parse(port.GetProtocol()) == protocol.Unsupported {
+ v = appendValidation(v, fmt.Errorf("ports[%d].protocol
%q is unsupported", i, port.GetProtocol()))
+ }
+ }
+ if spec.GetWorkloadSelector() != nil && len(spec.GetEndpoints()) > 0 {
+ v = appendValidation(v, fmt.Errorf("only one of
workloadSelector or endpoints can be set"))
+ }
+ v = appendValidation(v,
validateWorkloadSelector(spec.GetWorkloadSelector()))
+ for i, endpoint := range spec.GetEndpoints() {
+ v = appendValidation(v,
validateWorkloadEntry(fmt.Sprintf("endpoints[%d]", i), endpoint))
+ }
+ seenExport := make(map[string]struct{}, len(spec.GetExportTo()))
+ for i, export := range spec.GetExportTo() {
+ if err := visibility.Instance(export).Validate(); err != nil {
+ v = appendValidation(v, fmt.Errorf("exportTo[%d]: %v",
i, err))
+ }
+ if _, found := seenExport[export]; found {
+ v = appendValidation(v, fmt.Errorf("exportTo[%d] %q is
duplicated", i, export))
+ }
+ seenExport[export] = struct{}{}
+ }
+ return v.Unwrap()
+})
+
+// ValidateWorkloadEntry checks that a WorkloadEntry can produce a routable
endpoint.
+var ValidateWorkloadEntry = RegisterValidateFunc("ValidateWorkloadEntry",
func(cfg config.Config) (Warning, error) {
+ spec, ok := cfg.Spec.(*networking.WorkloadEntry)
+ if !ok {
+ return nil, fmt.Errorf("cannot cast to WorkloadEntry")
+ }
+ v := Validation{}
+ v = appendValidation(v, validateWorkloadEntry("workloadEntry", spec))
+ return v.Unwrap()
+})
+
+func validateServiceEntryHost(field, value string) error {
+ if value == "" {
+ return fmt.Errorf("%s must not be empty", field)
+ }
+ if net.ParseIP(value) != nil {
+ return nil
+ }
+ hostname := strings.TrimPrefix(value, "*.")
+ if messages := kvalidation.IsDNS1123Subdomain(hostname); len(messages)
> 0 {
+ return fmt.Errorf("%s %q is not a valid DNS name: %v", field,
value, messages)
+ }
+ if strings.Contains(value, "*") && !strings.HasPrefix(value, "*.") {
+ return fmt.Errorf("%s %q has an invalid wildcard", field, value)
+ }
+ return nil
+}
+
+func validateWorkloadEntry(field string, workload *networking.WorkloadEntry)
error {
+ if workload == nil {
+ return fmt.Errorf("%s must not be null", field)
+ }
+ var errs error
+ if err := validateServiceEntryHost(field+".address",
workload.GetAddress()); err != nil {
+ errs = AppendErrors(errs, err)
+ }
+ for name, port := range workload.GetPorts() {
+ if name == "" {
+ errs = AppendErrors(errs, fmt.Errorf("%s.ports contains
an empty name", field))
+ }
+ if port == 0 || port > 65535 {
+ errs = AppendErrors(errs, fmt.Errorf("%s.ports[%q] must
be between 1 and 65535", field, name))
+ }
+ }
+ if err := labels.Instance(workload.GetLabels()).Validate(); err != nil {
+ errs = AppendErrors(errs, fmt.Errorf("%s.labels: %v", field,
err))
+ }
+ return errs
+}
+
// ValidateTelemetry checks that a Telemetry resource is well-formed.
var ValidateTelemetry = RegisterValidateFunc("ValidateTelemetry",
func(cfg config.Config) (Warning, error) {
diff --git a/pkg/config/validation/validators_test.go
b/pkg/config/validation/validators_test.go
index a8d2c9f2..c6276608 100644
--- a/pkg/config/validation/validators_test.go
+++ b/pkg/config/validation/validators_test.go
@@ -295,6 +295,68 @@ func TestValidateCircuitBreakerPolicy(t *testing.T) {
}
}
+func TestValidateServiceEntry(t *testing.T) {
+ valid := func() *networking.ServiceEntry {
+ return &networking.ServiceEntry{
+ Hosts: []string{"api.example.com"},
+ Addresses: []string{"240.0.0.1"},
+ Resolution: networking.ServiceEntry_STATIC,
+ Ports: []*networking.ServicePort{{Name: "tcp",
Number: 443, Protocol: "TCP"}},
+ Endpoints: []*networking.WorkloadEntry{{Address:
"10.0.0.1", Ports: map[string]uint32{"tcp": 8443}}},
+ }
+ }
+ cases := []struct {
+ name string
+ mutate func(*networking.ServiceEntry)
+ wantErr bool
+ }{
+ {name: "valid", mutate: func(*networking.ServiceEntry) {},
wantErr: false},
+ {name: "missing host", mutate: func(s *networking.ServiceEntry)
{ s.Hosts = nil }, wantErr: true},
+ {name: "bad address", mutate: func(s *networking.ServiceEntry)
{ s.Addresses = []string{"not-an-ip"} }, wantErr: true},
+ {name: "duplicate port", mutate: func(s
*networking.ServiceEntry) {
+ s.Ports = append(s.Ports, &networking.ServicePort{Name:
"tcp", Number: 443, Protocol: "TCP"})
+ }, wantErr: true},
+ {name: "unsupported protocol", mutate: func(s
*networking.ServiceEntry) { s.Ports[0].Protocol = "SMTP" }, wantErr: true},
+ {name: "selector and endpoints", mutate: func(s
*networking.ServiceEntry) {
+ s.WorkloadSelector =
&typev1alpha3.WorkloadSelector{MatchLabels: map[string]string{"app": "api"}}
+ }, wantErr: true},
+ {name: "invalid endpoint", mutate: func(s
*networking.ServiceEntry) { s.Endpoints[0].Address = "" }, wantErr: true},
+ {name: "invalid export", mutate: func(s
*networking.ServiceEntry) { s.ExportTo = []string{"bad_namespace"} }, wantErr:
true},
+ }
+ for _, tc := range cases {
+ t.Run(tc.name, func(t *testing.T) {
+ spec := valid()
+ tc.mutate(spec)
+ _, err := ValidateServiceEntry(makeConfig(spec))
+ if got := err != nil; got != tc.wantErr {
+ t.Fatalf("ValidateServiceEntry() error = %v,
wantErr = %v", err, tc.wantErr)
+ }
+ })
+ }
+}
+
+func TestValidateWorkloadEntry(t *testing.T) {
+ cases := []struct {
+ name string
+ spec *networking.WorkloadEntry
+ wantErr bool
+ }{
+ {name: "valid IP", spec: &networking.WorkloadEntry{Address:
"10.0.0.1", Ports: map[string]uint32{"grpc": 50051}}, wantErr: false},
+ {name: "valid DNS", spec: &networking.WorkloadEntry{Address:
"vm.example.com"}, wantErr: false},
+ {name: "missing address", spec: &networking.WorkloadEntry{},
wantErr: true},
+ {name: "invalid port", spec: &networking.WorkloadEntry{Address:
"10.0.0.1", Ports: map[string]uint32{"grpc": 0}}, wantErr: true},
+ {name: "invalid labels", spec:
&networking.WorkloadEntry{Address: "10.0.0.1", Labels: map[string]string{"bad
key": "value"}}, wantErr: true},
+ }
+ for _, tc := range cases {
+ t.Run(tc.name, func(t *testing.T) {
+ _, err := ValidateWorkloadEntry(makeConfig(tc.spec))
+ if got := err != nil; got != tc.wantErr {
+ t.Fatalf("ValidateWorkloadEntry() error = %v,
wantErr = %v", err, tc.wantErr)
+ }
+ })
+ }
+}
+
func TestValidateTelemetry(t *testing.T) {
cases := []struct {
name string