This is an automated email from the ASF dual-hosted git repository.
mfordjody pushed a commit to branch chore_v64
in repository https://gitbox.apache.org/repos/asf/dubbo-kubernetes.git
The following commit(s) were added to refs/heads/chore_v64 by this push:
new 9dfbefce Complete KRT controllers framework
9dfbefce is described below
commit 9dfbefce5cf45d941c0bfd3f21589085f836bc92
Author: mfordjody <[email protected]>
AuthorDate: Tue Jun 30 09:17:17 2026 +0800
Complete KRT controllers framework
---
dubbod/discovery/pkg/bootstrap/server.go | 3 +
.../discovery/pkg/config/kube/crdclient/client.go | 9 +-
.../pkg/config/kube/crdclient/client_test.go | 72 +++++
.../pkg/config/kube/crdclient/types.gen.go | 46 +++
.../discovery/pkg/config/kube/file/controller.go | 3 +-
.../pkg/config/kube/file/controller_test.go | 92 ++++++
.../pkg/config/kube/gateway/controller.go | 137 ++++++---
.../pkg/config/kube/gateway/controller_test.go | 336 +++++++++++++++++++++
.../pkg/config/kube/gateway/gateways_collection.go | 38 ++-
.../pkg/config/kube/gateway/referencegrants.go | 187 ++++++++++++
dubbod/discovery/pkg/model/context.go | 2 +
pkg/config/schema/collections/collections.gen.go | 23 ++
pkg/config/schema/gvk/resources.gen.go | 10 +
pkg/config/schema/gvr/resources.gen.go | 6 +
pkg/config/schema/kind/resources.gen.go | 5 +
pkg/config/schema/kubeclient/resources.gen.go | 14 +
pkg/config/schema/kubetypes/resources.gen.go | 3 +
pkg/config/schema/metadata.yaml | 9 +
18 files changed, 938 insertions(+), 57 deletions(-)
diff --git a/dubbod/discovery/pkg/bootstrap/server.go
b/dubbod/discovery/pkg/bootstrap/server.go
index d904759d..fd19f73a 100644
--- a/dubbod/discovery/pkg/bootstrap/server.go
+++ b/dubbod/discovery/pkg/bootstrap/server.go
@@ -505,6 +505,8 @@ func (s *Server) initRegistryEventHandlers() {
configKind = kind.PeerAuthentication
case "RequestAuthentication":
configKind = kind.RequestAuthentication
+ case "ReferenceGrant":
+ configKind = kind.ReferenceGrant
case "GatewayClass":
configKind = kind.GatewayClass
case "KubernetesGateway":
@@ -538,6 +540,7 @@ func (s *Server) initRegistryEventHandlers() {
configKind == kind.AuthorizationPolicy ||
configKind == kind.HTTPRoute ||
configKind == kind.BackendTLSPolicy ||
+ configKind == kind.ReferenceGrant ||
configKind == kind.CircuitBreakerPolicy
// Trigger ConfigUpdate to push changes to all connected proxies
diff --git a/dubbod/discovery/pkg/config/kube/crdclient/client.go
b/dubbod/discovery/pkg/config/kube/crdclient/client.go
index 6e6f7cb7..28760a93 100644
--- a/dubbod/discovery/pkg/config/kube/crdclient/client.go
+++ b/dubbod/discovery/pkg/config/kube/crdclient/client.go
@@ -19,10 +19,11 @@ package crdclient
import (
"encoding/json"
"fmt"
- "github.com/apache/dubbo-kubernetes/pkg/config/schema/resource"
"sync"
"time"
+ "github.com/apache/dubbo-kubernetes/pkg/config/schema/resource"
+
"github.com/apache/dubbo-kubernetes/dubbod/discovery/pkg/model"
"github.com/apache/dubbo-kubernetes/pkg/config"
"github.com/apache/dubbo-kubernetes/pkg/config/schema/collection"
@@ -249,7 +250,8 @@ func (cl *Client) Schemas() collection.Schemas {
}
func (cl *Client) RegisterEventHandler(kind config.GroupVersionKind, handler
model.EventHandler) {
- if c, ok := cl.kind(kind); ok {
+ cl.kindsMu.Lock()
+ if c, ok := cl.kinds[kind]; ok {
c.handlers = append(c.handlers,
c.collection.RegisterBatch(func(o []krt.Event[config.Config]) {
for _, event := range o {
switch event.Event {
@@ -262,8 +264,11 @@ func (cl *Client) RegisterEventHandler(kind
config.GroupVersionKind, handler mod
}
}
}, false))
+ cl.kinds[kind] = c
+ cl.kindsMu.Unlock()
return
}
+ cl.kindsMu.Unlock()
cl.logger.Warnf("unknown type: %s", kind)
}
diff --git a/dubbod/discovery/pkg/config/kube/crdclient/client_test.go
b/dubbod/discovery/pkg/config/kube/crdclient/client_test.go
new file mode 100644
index 00000000..c3439123
--- /dev/null
+++ b/dubbod/discovery/pkg/config/kube/crdclient/client_test.go
@@ -0,0 +1,72 @@
+// 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 crdclient
+
+import (
+ "testing"
+ "time"
+
+ "github.com/apache/dubbo-kubernetes/dubbod/discovery/pkg/model"
+ "github.com/apache/dubbo-kubernetes/pkg/config"
+ "github.com/apache/dubbo-kubernetes/pkg/config/schema/gvk"
+ "github.com/apache/dubbo-kubernetes/pkg/kube/krt"
+ gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
+)
+
+func TestRegisterEventHandlerPersistsRegistration(t *testing.T) {
+ stop := make(chan struct{})
+ t.Cleanup(func() { close(stop) })
+
+ routes := krt.NewStaticCollection[config.Config](nil, nil,
krt.WithStop(stop))
+ cl := &Client{
+ kinds: map[config.GroupVersionKind]nsStore{
+ gvk.HTTPRoute: {
+ collection: routes,
+ index: krt.NewNamespaceIndex(routes),
+ },
+ },
+ }
+ events := make(chan model.Event, 1)
+ cl.RegisterEventHandler(gvk.HTTPRoute, func(_, _ config.Config, event
model.Event) {
+ events <- event
+ })
+
+ if got := len(cl.kinds[gvk.HTTPRoute].handlers); got != 1 {
+ t.Fatalf("registered handlers = %d, want 1", got)
+ }
+
+ routes.UpdateObject(config.Config{
+ Meta: config.Meta{
+ GroupVersionKind: gvk.HTTPRoute,
+ Name: "orders",
+ Namespace: "app",
+ },
+ Spec: &gatewayv1.HTTPRouteSpec{},
+ })
+ expectEvent(t, events, model.EventAdd)
+}
+
+func expectEvent(t *testing.T, events <-chan model.Event, want model.Event) {
+ t.Helper()
+ select {
+ case got := <-events:
+ if got != want {
+ t.Fatalf("event = %v, want %v", got, want)
+ }
+ case <-time.After(time.Second):
+ t.Fatalf("expected event handler to receive %v event", want)
+ }
+}
diff --git a/dubbod/discovery/pkg/config/kube/crdclient/types.gen.go
b/dubbod/discovery/pkg/config/kube/crdclient/types.gen.go
index 3d9de0e7..66927ce2 100755
--- a/dubbod/discovery/pkg/config/kube/crdclient/types.gen.go
+++ b/dubbod/discovery/pkg/config/kube/crdclient/types.gen.go
@@ -28,6 +28,7 @@ import (
k8sioapipolicyv1 "k8s.io/api/policy/v1"
k8sioapiextensionsapiserverpkgapisapiextensionsv1
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
sigsk8siogatewayapiapisv1 "sigs.k8s.io/gateway-api/apis/v1"
+ sigsk8siogatewayapiapisv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
)
func create(c kube.Client, cfg config.Config, objMeta metav1.ObjectMeta)
(metav1.Object, error) {
@@ -67,6 +68,11 @@ func create(c kube.Client, cfg config.Config, objMeta
metav1.ObjectMeta) (metav1
ObjectMeta: objMeta,
Spec:
*(cfg.Spec.(*githubcomkdubboapisecurityv1alpha3.PeerAuthentication)),
}, metav1.CreateOptions{})
+ case gvk.ReferenceGrant:
+ return
c.GatewayAPI().GatewayV1beta1().ReferenceGrants(cfg.Namespace).Create(context.TODO(),
&sigsk8siogatewayapiapisv1beta1.ReferenceGrant{
+ ObjectMeta: objMeta,
+ Spec:
*(cfg.Spec.(*sigsk8siogatewayapiapisv1beta1.ReferenceGrantSpec)),
+ }, metav1.CreateOptions{})
case gvk.RequestAuthentication:
return
c.Dubbo().SecurityV1alpha3().RequestAuthentications(cfg.Namespace).Create(context.TODO(),
&apigithubcomapachedubbokubernetesapisecurityv1alpha3.RequestAuthentication{
ObjectMeta: objMeta,
@@ -114,6 +120,11 @@ func update(c kube.Client, cfg config.Config, objMeta
metav1.ObjectMeta) (metav1
ObjectMeta: objMeta,
Spec:
*(cfg.Spec.(*githubcomkdubboapisecurityv1alpha3.PeerAuthentication)),
}, metav1.UpdateOptions{})
+ case gvk.ReferenceGrant:
+ return
c.GatewayAPI().GatewayV1beta1().ReferenceGrants(cfg.Namespace).Update(context.TODO(),
&sigsk8siogatewayapiapisv1beta1.ReferenceGrant{
+ ObjectMeta: objMeta,
+ Spec:
*(cfg.Spec.(*sigsk8siogatewayapiapisv1beta1.ReferenceGrantSpec)),
+ }, metav1.UpdateOptions{})
case gvk.RequestAuthentication:
return
c.Dubbo().SecurityV1alpha3().RequestAuthentications(cfg.Namespace).Update(context.TODO(),
&apigithubcomapachedubbokubernetesapisecurityv1alpha3.RequestAuthentication{
ObjectMeta: objMeta,
@@ -281,6 +292,21 @@ func patch(c kube.Client, orig config.Config, origMeta
metav1.ObjectMeta, mod co
}
return
c.Dubbo().SecurityV1alpha3().PeerAuthentications(orig.Namespace).
Patch(context.TODO(), orig.Name, typ, patchBytes,
metav1.PatchOptions{FieldManager: "pilot-discovery"})
+ case gvk.ReferenceGrant:
+ oldRes := &sigsk8siogatewayapiapisv1beta1.ReferenceGrant{
+ ObjectMeta: origMeta,
+ Spec:
*(orig.Spec.(*sigsk8siogatewayapiapisv1beta1.ReferenceGrantSpec)),
+ }
+ modRes := &sigsk8siogatewayapiapisv1beta1.ReferenceGrant{
+ ObjectMeta: modMeta,
+ Spec:
*(mod.Spec.(*sigsk8siogatewayapiapisv1beta1.ReferenceGrantSpec)),
+ }
+ patchBytes, err := genPatchBytes(oldRes, modRes, typ)
+ if err != nil {
+ return nil, err
+ }
+ return
c.GatewayAPI().GatewayV1beta1().ReferenceGrants(orig.Namespace).
+ Patch(context.TODO(), orig.Name, typ, patchBytes,
metav1.PatchOptions{FieldManager: "pilot-discovery"})
case gvk.RequestAuthentication:
oldRes :=
&apigithubcomapachedubbokubernetesapisecurityv1alpha3.RequestAuthentication{
ObjectMeta: origMeta,
@@ -321,6 +347,8 @@ func delete(c kube.Client, typ config.GroupVersionKind,
name, namespace string,
return
c.GatewayAPI().GatewayV1().Gateways(namespace).Delete(context.TODO(), name,
deleteOptions)
case gvk.PeerAuthentication:
return
c.Dubbo().SecurityV1alpha3().PeerAuthentications(namespace).Delete(context.TODO(),
name, deleteOptions)
+ case gvk.ReferenceGrant:
+ 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)
default:
@@ -698,6 +726,24 @@ var translationMap = map[config.GroupVersionKind]func(r
runtime.Object) config.C
Status: &obj.Status,
}
},
+ gvk.ReferenceGrant: func(r runtime.Object) config.Config {
+ obj := r.(*sigsk8siogatewayapiapisv1beta1.ReferenceGrant)
+ return config.Config{
+ Meta: config.Meta{
+ GroupVersionKind: gvk.ReferenceGrant,
+ 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,
+ }
+ },
gvk.RequestAuthentication: func(r runtime.Object) config.Config {
obj :=
r.(*apigithubcomapachedubbokubernetesapisecurityv1alpha3.RequestAuthentication)
return config.Config{
diff --git a/dubbod/discovery/pkg/config/kube/file/controller.go
b/dubbod/discovery/pkg/config/kube/file/controller.go
index 43ef5ded..6c7613e0 100644
--- a/dubbod/discovery/pkg/config/kube/file/controller.go
+++ b/dubbod/discovery/pkg/config/kube/file/controller.go
@@ -154,6 +154,7 @@ func (c *Controller) RegisterEventHandler(typ
config.GroupVersionKind, handler m
}
}, false),
)
+ c.data[typ] = data
}
}
@@ -183,7 +184,7 @@ func (c ConfigKind) ResourceName() string {
return c.GroupVersionKind.String() + "/" + c.Name
}
- return c.GroupVersionKind.String() + "/" + c.Namespace + c.Name
+ return c.GroupVersionKind.String() + "/" + c.Namespace + "/" + c.Name
}
func (c ConfigKind) Equals(other ConfigKind) bool {
diff --git a/dubbod/discovery/pkg/config/kube/file/controller_test.go
b/dubbod/discovery/pkg/config/kube/file/controller_test.go
new file mode 100644
index 00000000..c61d97d0
--- /dev/null
+++ b/dubbod/discovery/pkg/config/kube/file/controller_test.go
@@ -0,0 +1,92 @@
+// 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 file
+
+import (
+ "testing"
+ "time"
+
+ "github.com/apache/dubbo-kubernetes/dubbod/discovery/pkg/model"
+ "github.com/apache/dubbo-kubernetes/pkg/config"
+ "github.com/apache/dubbo-kubernetes/pkg/config/schema/gvk"
+ "github.com/apache/dubbo-kubernetes/pkg/kube/krt"
+ gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
+)
+
+func TestRegisterEventHandlerPersistsRegistration(t *testing.T) {
+ stop := make(chan struct{})
+ t.Cleanup(func() { close(stop) })
+
+ routes := krt.NewStaticCollection[config.Config](nil, nil,
krt.WithStop(stop))
+ c := &Controller{
+ data: map[config.GroupVersionKind]kindStore{
+ gvk.HTTPRoute: {
+ collection: routes,
+ index: krt.NewNamespaceIndex(routes),
+ },
+ },
+ }
+ events := make(chan model.Event, 1)
+ c.RegisterEventHandler(gvk.HTTPRoute, func(_, _ config.Config, event
model.Event) {
+ events <- event
+ })
+
+ if got := len(c.data[gvk.HTTPRoute].handlers); got != 1 {
+ t.Fatalf("registered handlers = %d, want 1", got)
+ }
+
+ routes.UpdateObject(config.Config{
+ Meta: config.Meta{
+ GroupVersionKind: gvk.HTTPRoute,
+ Name: "orders",
+ Namespace: "app",
+ },
+ Spec: &gatewayv1.HTTPRouteSpec{},
+ })
+ expectEvent(t, events, model.EventAdd)
+}
+
+func TestConfigKindResourceNameUsesNamespaceSeparator(t *testing.T) {
+ first := ConfigKind{&config.Config{Meta: config.Meta{
+ GroupVersionKind: gvk.HTTPRoute,
+ Name: "c",
+ Namespace: "ab",
+ }}}
+ second := ConfigKind{&config.Config{Meta: config.Meta{
+ GroupVersionKind: gvk.HTTPRoute,
+ Name: "bc",
+ Namespace: "a",
+ }}}
+
+ if got, want := first.ResourceName(), gvk.HTTPRoute.String()+"/ab/c";
got != want {
+ t.Fatalf("resource name = %q, want %q", got, want)
+ }
+ if first.ResourceName() == second.ResourceName() {
+ t.Fatalf("resource names collided: %q", first.ResourceName())
+ }
+}
+
+func expectEvent(t *testing.T, events <-chan model.Event, want model.Event) {
+ t.Helper()
+ select {
+ case got := <-events:
+ if got != want {
+ t.Fatalf("event = %v, want %v", got, want)
+ }
+ case <-time.After(time.Second):
+ t.Fatalf("expected event handler to receive %v event", want)
+ }
+}
diff --git a/dubbod/discovery/pkg/config/kube/gateway/controller.go
b/dubbod/discovery/pkg/config/kube/gateway/controller.go
index 36eb733a..37c95150 100644
--- a/dubbod/discovery/pkg/config/kube/gateway/controller.go
+++ b/dubbod/discovery/pkg/config/kube/gateway/controller.go
@@ -41,6 +41,7 @@ import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
gateway "sigs.k8s.io/gateway-api/apis/v1"
+ gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
)
var log = dubbolog.RegisterScope("gateway", "gateway-api controller")
@@ -55,24 +56,33 @@ type Controller struct {
gatewayContext krt.RecomputeProtected[*atomic.Pointer[Context]]
stop chan struct{}
xdsUpdater model.XDSUpdater
- handlers []krt.HandlerRegistration
outputs Outputs
+ data map[config.GroupVersionKind]kindStore
domainSuffix string
status *status.StatusCollections
inputs Inputs
}
type Outputs struct {
- Gateways krt.Collection[Gateway]
+ Gateways krt.Collection[config.Config]
+ HTTPRoutes krt.Collection[config.Config]
+ ReferenceGrants referenceGrantStore
+}
+
+type kindStore struct {
+ collection krt.Collection[config.Config]
+ index krt.Index[string, config.Config]
+ handlers []krt.HandlerRegistration
}
type Inputs struct {
Services krt.Collection[*corev1.Service]
Namespaces krt.Collection[*corev1.Namespace]
- GatewayClasses krt.Collection[*gateway.GatewayClass]
- Gateways krt.Collection[*gateway.Gateway]
- HTTPRoutes krt.Collection[*gateway.HTTPRoute]
+ GatewayClasses krt.Collection[*gateway.GatewayClass]
+ Gateways krt.Collection[*gateway.Gateway]
+ HTTPRoutes krt.Collection[*gateway.HTTPRoute]
+ ReferenceGrants krt.Collection[*gatewayv1beta1.ReferenceGrant]
}
func NewController(kc kube.Client, waitForCRD func(class
schema.GroupVersionResource, stop <-chan struct{}) bool, options
controller.Options, xdsUpdater model.XDSUpdater) *Controller {
@@ -99,6 +109,13 @@ func NewController(kc kube.Client, waitForCRD func(class
schema.GroupVersionReso
GatewayClasses: buildClient[*gateway.GatewayClass](c, kc,
gvr.GatewayClass, opts, "informer/GatewayClasses"),
Gateways: buildClient[*gateway.Gateway](c, kc,
gvr.KubernetesGateway, opts, "informer/Gateways"),
HTTPRoutes: buildClient[*gateway.HTTPRoute](c, kc,
gvr.HTTPRoute, opts, "informer/HTTPRoutes"),
+ ReferenceGrants: buildClient[*gatewayv1beta1.ReferenceGrant](
+ c,
+ kc,
+ gvr.ReferenceGrant,
+ opts,
+ "informer/ReferenceGrants",
+ ),
}
GatewayClassStatus, GatewayClasses :=
GatewayClassesCollection(inputs.GatewayClasses, opts)
@@ -113,18 +130,37 @@ func NewController(kc kube.Client, waitForCRD func(class
schema.GroupVersionReso
GatewayFinalStatus := FinalGatewayStatusCollection(GatewaysStatus, opts)
status.RegisterStatus(c.status, GatewayFinalStatus, GetStatus)
- handlers := []krt.HandlerRegistration{}
+ ReferenceGrants :=
newReferenceGrantStore(ReferenceGrantsCollection(inputs.ReferenceGrants, opts))
+
+ HTTPRoutes := krt.NewCollection(inputs.HTTPRoutes, func(ctx
krt.HandlerContext, hr *gateway.HTTPRoute) *config.Config {
+ cfg := convertHTTPRouteToConfig(hr)
+ return &cfg
+ }, opts.WithName("HTTPRoutes")...)
+
outputs := Outputs{
- Gateways: Gateways,
+ Gateways: Gateways,
+ HTTPRoutes: HTTPRoutes,
+ ReferenceGrants: ReferenceGrants,
+ }
+ data := map[config.GroupVersionKind]kindStore{
+ gvk.KubernetesGateway: newKindStore(Gateways),
+ gvk.HTTPRoute: newKindStore(HTTPRoutes),
}
c.outputs = outputs
- c.handlers = handlers
+ c.data = data
c.inputs = inputs
return c
}
+func newKindStore(collection krt.Collection[config.Config]) kindStore {
+ return kindStore{
+ collection: collection,
+ index: krt.NewNamespaceIndex(collection),
+ }
+}
+
func (c *Controller) Reconcile(ps *model.PushContext) {
ctx := NewGatewayContext(ps, c.cluster)
c.gatewayContext.Modify(func(i **atomic.Pointer[Context]) {
@@ -145,25 +181,23 @@ func (c *Controller) SetStatusWrite(enabled bool,
statusManager *status.Manager)
}
func (c *Controller) Get(typ config.GroupVersionKind, name, namespace string)
*config.Config {
+ if data, ok := c.data[typ]; ok {
+ if namespace == "" {
+ return data.collection.GetKey(name)
+ }
+ return data.collection.GetKey(namespace + "/" + name)
+ }
return nil
}
func (c *Controller) List(typ config.GroupVersionKind, namespace string)
[]config.Config {
- switch typ {
- case gvk.HTTPRoute:
- // Convert HTTPRoute collection to config.Config list
- httpRoutes := c.inputs.HTTPRoutes.List()
- result := make([]config.Config, 0, len(httpRoutes))
- for _, hr := range httpRoutes {
- cfg := convertHTTPRouteToConfig(hr)
- if namespace == "" || cfg.Namespace == namespace {
- result = append(result, cfg)
- }
+ if data, ok := c.data[typ]; ok {
+ if namespace == "" {
+ return data.collection.List()
}
- return result
- default:
- return nil
+ return data.index.Lookup(namespace)
}
+ return nil
}
func convertHTTPRouteToConfig(hr *gateway.HTTPRoute) config.Config {
@@ -180,8 +214,8 @@ func convertHTTPRouteToConfig(hr *gateway.HTTPRoute)
config.Config {
UID: string(hr.UID),
Generation: hr.Generation,
},
- Spec: &hr.Spec,
- Status: &hr.Status,
+ Spec: hr.Spec.DeepCopy(),
+ Status: hr.Status.DeepCopy(),
}
}
@@ -206,36 +240,57 @@ func (c *Controller) Delete(typ config.GroupVersionKind,
name, namespace string,
}
func (c *Controller) HasSynced() bool {
- // Check if all input collections are synced
- if !c.inputs.Services.HasSynced() {
- return false
- }
- if !c.inputs.Namespaces.HasSynced() {
- return false
- }
- if !c.inputs.GatewayClasses.HasSynced() {
+ if !collectionHasSynced(c.inputs.Services) ||
+ !collectionHasSynced(c.inputs.Namespaces) ||
+ !collectionHasSynced(c.inputs.GatewayClasses) ||
+ !collectionHasSynced(c.inputs.Gateways) ||
+ !collectionHasSynced(c.inputs.HTTPRoutes) ||
+ !collectionHasSynced(c.inputs.ReferenceGrants) {
return false
}
- if !c.inputs.Gateways.HasSynced() {
- return false
- }
- if !c.inputs.HTTPRoutes.HasSynced() {
+
+ if c.outputs.ReferenceGrants.Collection != nil &&
!c.outputs.ReferenceGrants.Collection.HasSynced() {
return false
}
- for _, h := range c.handlers {
- if !h.HasSynced() {
+
+ for _, data := range c.data {
+ if !data.collection.HasSynced() {
return false
}
+ for _, handler := range data.handlers {
+ if !handler.HasSynced() {
+ return false
+ }
+ }
}
return true
}
+func collectionHasSynced[T any](collection krt.Collection[T]) bool {
+ return collection == nil || collection.HasSynced()
+}
+
func (c *Controller) RegisterEventHandler(typ config.GroupVersionKind, handler
model.EventHandler) {
- // We do not do event handler registration this way, and instead
directly call the XDS Updated.
+ if data, ok := c.data[typ]; ok {
+ data.handlers = append(data.handlers,
data.collection.RegisterBatch(func(evs []krt.Event[config.Config]) {
+ for _, event := range evs {
+ switch event.Event {
+ case controllers.EventAdd:
+ handler(config.Config{}, *event.New,
model.EventAdd)
+ case controllers.EventUpdate:
+ handler(*event.Old, *event.New,
model.EventUpdate)
+ case controllers.EventDelete:
+ handler(config.Config{}, *event.Old,
model.EventDelete)
+ }
+ }
+ }, false))
+ c.data[typ] = data
+ }
}
func (c *Controller) Schemas() collection.Schemas {
return collection.SchemasFor(
+ collections.KubernetesGateway,
collections.HTTPRoute,
)
}
@@ -255,7 +310,7 @@ func (c *Controller) Run(stop <-chan struct{}) {
}
func (c *Controller) SecretAllowed(ourKind config.GroupVersionKind,
resourceName string, namespace string) bool {
- return true // TODO
+ return c.outputs.ReferenceGrants.SecretAllowed(nil, ourKind,
resourceName, namespace)
}
func buildClient[I controllers.ComparableObject](
@@ -269,8 +324,8 @@ func buildClient[I controllers.ComparableObject](
ObjectFilter: kubetypes.ComposeFilters(kc.ObjectFilter(),
c.inRevision),
}
- // Gateway and HTTPRoute are not filtered by revision, they need to
select tags as well
- if res == gvr.KubernetesGateway || res == gvr.HTTPRoute {
+ // Gateway API resources select revisions through their
parent/attachment semantics.
+ if res == gvr.KubernetesGateway || res == gvr.HTTPRoute || res ==
gvr.ReferenceGrant {
filter.ObjectFilter = kc.ObjectFilter()
}
diff --git a/dubbod/discovery/pkg/config/kube/gateway/controller_test.go
b/dubbod/discovery/pkg/config/kube/gateway/controller_test.go
new file mode 100644
index 00000000..c24afa58
--- /dev/null
+++ b/dubbod/discovery/pkg/config/kube/gateway/controller_test.go
@@ -0,0 +1,336 @@
+//
+// 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 gateway
+
+import (
+ "testing"
+ "time"
+
+ "github.com/apache/dubbo-kubernetes/dubbod/discovery/pkg/features"
+ "github.com/apache/dubbo-kubernetes/dubbod/discovery/pkg/model"
+ "github.com/apache/dubbo-kubernetes/pkg/config"
+ "github.com/apache/dubbo-kubernetes/pkg/config/schema/gvk"
+ "github.com/apache/dubbo-kubernetes/pkg/kube/krt"
+ corev1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
+ gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
+)
+
+func TestGatewayControllerConfigStore(t *testing.T) {
+ stop := make(chan struct{})
+ t.Cleanup(func() { close(stop) })
+
+ gatewayConfigs := krt.NewStaticCollection[config.Config](nil,
[]config.Config{
+ {
+ Meta: config.Meta{
+ GroupVersionKind: gvk.KubernetesGateway,
+ Name: "public",
+ Namespace: "app",
+ },
+ Spec: &gatewayv1.GatewaySpec{},
+ },
+ }, krt.WithStop(stop))
+ httpRouteConfigs := krt.NewStaticCollection[config.Config](nil,
[]config.Config{
+ {
+ Meta: config.Meta{
+ GroupVersionKind: gvk.HTTPRoute,
+ Name: "orders",
+ Namespace: "app",
+ },
+ Spec: &gatewayv1.HTTPRouteSpec{},
+ },
+ {
+ Meta: config.Meta{
+ GroupVersionKind: gvk.HTTPRoute,
+ Name: "admin",
+ Namespace: "ops",
+ },
+ Spec: &gatewayv1.HTTPRouteSpec{},
+ },
+ }, krt.WithStop(stop))
+ c := &Controller{
+ data: map[config.GroupVersionKind]kindStore{
+ gvk.KubernetesGateway: newKindStore(gatewayConfigs),
+ gvk.HTTPRoute: newKindStore(httpRouteConfigs),
+ },
+ }
+
+ if _, ok := c.Schemas().FindByGroupVersionKind(gvk.KubernetesGateway);
!ok {
+ t.Fatal("schemas missing KubernetesGateway")
+ }
+ if _, ok := c.Schemas().FindByGroupVersionKind(gvk.HTTPRoute); !ok {
+ t.Fatal("schemas missing HTTPRoute")
+ }
+
+ if got := c.Get(gvk.KubernetesGateway, "public", "app"); got == nil ||
got.Name != "public" {
+ t.Fatalf("gateway Get() = %#v, want public", got)
+ }
+ if got := c.List(gvk.HTTPRoute, "app"); len(got) != 1 || got[0].Name !=
"orders" {
+ t.Fatalf("HTTPRoute namespace List() = %#v, want only
app/orders", got)
+ }
+ if got := c.List(gvk.HTTPRoute, metav1.NamespaceAll); len(got) != 2 {
+ t.Fatalf("HTTPRoute all-namespaces List() = %d, want 2",
len(got))
+ }
+}
+
+func TestGatewayControllerRegisterEventHandler(t *testing.T) {
+ stop := make(chan struct{})
+ t.Cleanup(func() { close(stop) })
+
+ routes := krt.NewStaticCollection[config.Config](nil, nil,
krt.WithStop(stop))
+ c := &Controller{
+ data: map[config.GroupVersionKind]kindStore{
+ gvk.HTTPRoute: newKindStore(routes),
+ },
+ }
+ events := make(chan model.Event, 1)
+ c.RegisterEventHandler(gvk.HTTPRoute, func(_, _ config.Config, event
model.Event) {
+ events <- event
+ })
+
+ routes.UpdateObject(config.Config{
+ Meta: config.Meta{
+ GroupVersionKind: gvk.HTTPRoute,
+ Name: "orders",
+ Namespace: "app",
+ },
+ Spec: &gatewayv1.HTTPRouteSpec{},
+ })
+ expectEvent(t, events, model.EventAdd)
+
+ routes.UpdateObject(config.Config{
+ Meta: config.Meta{
+ GroupVersionKind: gvk.HTTPRoute,
+ Name: "orders",
+ Namespace: "app",
+ Generation: 2,
+ },
+ Spec: &gatewayv1.HTTPRouteSpec{},
+ })
+ expectEvent(t, events, model.EventUpdate)
+
+ routes.DeleteObject("app/orders")
+ expectEvent(t, events, model.EventDelete)
+}
+
+func TestGatewayControllerSecretAllowed(t *testing.T) {
+ stop := make(chan struct{})
+ t.Cleanup(func() { close(stop) })
+ opts := krt.NewOptionsBuilder(stop, "test", nil)
+
+ secretName := gatewayv1.ObjectName("tls-cert")
+ grants := krt.NewStaticCollection[*gatewayv1beta1.ReferenceGrant](nil,
[]*gatewayv1beta1.ReferenceGrant{
+ {
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "allow-specific",
+ Namespace: "certs",
+ },
+ Spec: gatewayv1beta1.ReferenceGrantSpec{
+ From: []gatewayv1beta1.ReferenceGrantFrom{{
+ Group:
gatewayv1.Group(gvk.KubernetesGateway.Group),
+ Kind:
gatewayv1.Kind(gvk.KubernetesGateway.Kind),
+ Namespace: "ingress",
+ }},
+ To: []gatewayv1beta1.ReferenceGrantTo{{
+ Group:
gatewayv1.Group(gvk.Secret.Group),
+ Kind: gatewayv1.Kind(gvk.Secret.Kind),
+ Name: &secretName,
+ }},
+ },
+ },
+ {
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "allow-all",
+ Namespace: "shared-certs",
+ },
+ Spec: gatewayv1beta1.ReferenceGrantSpec{
+ From: []gatewayv1beta1.ReferenceGrantFrom{{
+ Group:
gatewayv1.Group(gvk.KubernetesGateway.Group),
+ Kind:
gatewayv1.Kind(gvk.KubernetesGateway.Kind),
+ Namespace: "ingress",
+ }},
+ To: []gatewayv1beta1.ReferenceGrantTo{{
+ Group:
gatewayv1.Group(gvk.Secret.Group),
+ Kind: gatewayv1.Kind(gvk.Secret.Kind),
+ }},
+ },
+ },
+ }, krt.WithStop(stop))
+ referenceGrants :=
newReferenceGrantStore(ReferenceGrantsCollection(grants, opts))
+ waitSynced(t, referenceGrants.Collection)
+ c := &Controller{
+ outputs: Outputs{
+ ReferenceGrants: referenceGrants,
+ },
+ }
+
+ if !c.SecretAllowed(gvk.KubernetesGateway,
"kubernetes-gateway://certs/tls-cert", "ingress") {
+ t.Fatal("SecretAllowed() = false, want true for specifically
granted Secret")
+ }
+ if !c.SecretAllowed(gvk.KubernetesGateway,
"kubernetes-gateway://shared-certs/any-cert", "ingress") {
+ t.Fatal("SecretAllowed() = false, want true for wildcard Secret
grant")
+ }
+ if c.SecretAllowed(gvk.KubernetesGateway,
"kubernetes-gateway://certs/other-cert", "ingress") {
+ t.Fatal("SecretAllowed() = true, want false for ungranted
Secret name")
+ }
+ if c.SecretAllowed(gvk.KubernetesGateway,
"kubernetes-gateway://certs/tls-cert", "other") {
+ t.Fatal("SecretAllowed() = true, want false for ungranted
source namespace")
+ }
+ if c.SecretAllowed(gvk.HTTPRoute,
"kubernetes-gateway://certs/tls-cert", "ingress") {
+ t.Fatal("SecretAllowed() = true, want false for ungranted
source kind")
+ }
+}
+
+func TestGatewayControllerHasSyncedChecksInputCollections(t *testing.T) {
+ stop := make(chan struct{})
+ t.Cleanup(func() { close(stop) })
+
+ services := krt.NewStaticCollection[*corev1.Service](unsyncedSyncer{},
nil, krt.WithStop(stop))
+ routes := krt.NewStaticCollection[config.Config](nil, nil,
krt.WithStop(stop))
+ c := &Controller{
+ inputs: Inputs{
+ Services: services,
+ },
+ data: map[config.GroupVersionKind]kindStore{
+ gvk.HTTPRoute: newKindStore(routes),
+ },
+ }
+
+ if c.HasSynced() {
+ t.Fatal("HasSynced() = true, want false while input collection
is still syncing")
+ }
+}
+
+func TestGatewaysCollectionEmitsManagedGatewayConfig(t *testing.T) {
+ stop := make(chan struct{})
+ t.Cleanup(func() { close(stop) })
+ opts := krt.NewOptionsBuilder(stop, "test", nil)
+
+ gateways := krt.NewStaticCollection[*gatewayv1.Gateway](nil,
[]*gatewayv1.Gateway{
+ {
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "public",
+ Namespace: "app",
+ },
+ Spec: gatewayv1.GatewaySpec{
+ GatewayClassName:
gatewayv1.ObjectName(features.GatewayAPIDefaultGatewayClass),
+ },
+ },
+ {
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "foreign",
+ Namespace: "app",
+ },
+ Spec: gatewayv1.GatewaySpec{
+ GatewayClassName: "external",
+ },
+ },
+ }, krt.WithStop(stop))
+ gatewayClasses := krt.NewStaticCollection[GatewayClass](nil, nil,
krt.WithStop(stop))
+
+ statuses, configs := GatewaysCollection(gateways, gatewayClasses, opts)
+ waitSynced(t, configs)
+ waitSynced(t, statuses)
+
+ got := configs.List()
+ if len(got) != 1 {
+ t.Fatalf("gateway configs = %#v, want only managed gateway",
got)
+ }
+ cfg := got[0]
+ if cfg.GroupVersionKind != gvk.KubernetesGateway || cfg.Name !=
"public" || cfg.Namespace != "app" {
+ t.Fatalf("gateway config meta = %#v, want app/public
KubernetesGateway", cfg.Meta)
+ }
+ if _, ok := cfg.Spec.(*gatewayv1.GatewaySpec); !ok {
+ t.Fatalf("gateway config spec type = %T, want
*gatewayv1.GatewaySpec", cfg.Spec)
+ }
+
+ statusList := statuses.List()
+ if len(statusList) != 1 {
+ t.Fatalf("gateway statuses = %#v, want only managed gateway
status", statusList)
+ }
+ status := statusList[0].Status
+ if !hasGatewayCondition(status,
string(gatewayv1.GatewayConditionAccepted), metav1.ConditionTrue) {
+ t.Fatalf("gateway status missing Accepted=True: %#v",
status.Conditions)
+ }
+ if !hasGatewayCondition(status,
string(gatewayv1.GatewayConditionProgrammed), metav1.ConditionTrue) {
+ t.Fatalf("gateway status missing Programmed=True: %#v",
status.Conditions)
+ }
+}
+
+func TestConvertHTTPRouteToConfigCopiesSpec(t *testing.T) {
+ route := &gatewayv1.HTTPRoute{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "orders",
+ Namespace: "app",
+ },
+ Spec: gatewayv1.HTTPRouteSpec{
+ Hostnames: []gatewayv1.Hostname{"orders.example.com"},
+ },
+ }
+ cfg := convertHTTPRouteToConfig(route)
+ route.Spec.Hostnames[0] = "mutated.example.com"
+
+ spec, ok := cfg.Spec.(*gatewayv1.HTTPRouteSpec)
+ if !ok {
+ t.Fatalf("HTTPRoute config spec type = %T, want
*gatewayv1.HTTPRouteSpec", cfg.Spec)
+ }
+ if got := spec.Hostnames[0]; got != "orders.example.com" {
+ t.Fatalf("HTTPRoute config hostname = %q, want deep-copied
value", got)
+ }
+}
+
+func expectEvent(t *testing.T, events <-chan model.Event, want model.Event) {
+ t.Helper()
+ select {
+ case got := <-events:
+ if got != want {
+ t.Fatalf("event = %v, want %v", got, want)
+ }
+ case <-time.After(time.Second):
+ t.Fatalf("expected event handler to receive %v event", want)
+ }
+}
+
+func waitSynced(t *testing.T, syncer krt.Syncer) {
+ t.Helper()
+ timeout := make(chan struct{})
+ timer := time.AfterFunc(2*time.Second, func() { close(timeout) })
+ defer timer.Stop()
+ if !syncer.WaitUntilSynced(timeout) {
+ t.Fatal("collection did not sync before timeout")
+ }
+}
+
+func hasGatewayCondition(status gatewayv1.GatewayStatus, conditionType string,
conditionStatus metav1.ConditionStatus) bool {
+ for _, condition := range status.Conditions {
+ if condition.Type == conditionType && condition.Status ==
conditionStatus {
+ return true
+ }
+ }
+ return false
+}
+
+type unsyncedSyncer struct{}
+
+func (unsyncedSyncer) HasSynced() bool {
+ return false
+}
+
+func (unsyncedSyncer) WaitUntilSynced(<-chan struct{}) bool {
+ return false
+}
diff --git a/dubbod/discovery/pkg/config/kube/gateway/gateways_collection.go
b/dubbod/discovery/pkg/config/kube/gateway/gateways_collection.go
index 9ef76500..1fb44b4e 100644
--- a/dubbod/discovery/pkg/config/kube/gateway/gateways_collection.go
+++ b/dubbod/discovery/pkg/config/kube/gateway/gateways_collection.go
@@ -19,29 +19,21 @@ package gateway
import (
"github.com/apache/dubbo-kubernetes/dubbod/discovery/pkg/model/kstatus"
"github.com/apache/dubbo-kubernetes/pkg/config"
+ "github.com/apache/dubbo-kubernetes/pkg/config/schema/gvk"
"github.com/apache/dubbo-kubernetes/pkg/kube/krt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
gateway "sigs.k8s.io/gateway-api/apis/v1"
)
-type Gateway struct {
- *config.Config `json:"config"`
-}
-
-func (g Gateway) ResourceName() string {
- return config.NamespacedName(g.Config).String()
-}
-
func GatewaysCollection(
gateways krt.Collection[*gateway.Gateway],
gatewayClasses krt.Collection[GatewayClass],
opts krt.OptionsBuilder,
) (
krt.StatusCollection[*gateway.Gateway, gateway.GatewayStatus],
- krt.Collection[Gateway],
+ krt.Collection[config.Config],
) {
- statusCol, gw := krt.NewStatusManyCollection(gateways, func(ctx
krt.HandlerContext, obj *gateway.Gateway) (*gateway.GatewayStatus, []Gateway) {
- result := []Gateway{}
+ statusCol, gw := krt.NewStatusCollection(gateways, func(ctx
krt.HandlerContext, obj *gateway.Gateway) (*gateway.GatewayStatus,
*config.Config) {
kgw := obj.Spec
status := obj.Status.DeepCopy()
class := fetchClass(ctx, gatewayClasses, kgw.GatewayClassName)
@@ -56,18 +48,38 @@ func GatewaysCollection(
if classInfo.disableRouteGeneration {
// For now we still mark the Gateway as accepted, but
let higher layers control route generation.
status = setGatewayConditions(status, obj.Generation,
true, true)
- return status, result
+ return status, nil
}
// Default behavior: GatewayClass is known and managed by this
controller.
// Mark Accepted/Programmed to ensure status no longer stays at
"Waiting for controller".
status = setGatewayConditions(status, obj.Generation, true,
true)
- return status, result
+ cfg := convertGatewayToConfig(obj)
+ return status, &cfg
}, opts.WithName("KubernetesGateway")...)
return statusCol, gw
}
+func convertGatewayToConfig(gw *gateway.Gateway) config.Config {
+ return config.Config{
+ Meta: config.Meta{
+ GroupVersionKind: gvk.KubernetesGateway,
+ Name: gw.Name,
+ Namespace: gw.Namespace,
+ Labels: gw.Labels,
+ Annotations: gw.Annotations,
+ ResourceVersion: gw.ResourceVersion,
+ CreationTimestamp: gw.CreationTimestamp.Time,
+ OwnerReferences: gw.OwnerReferences,
+ UID: string(gw.UID),
+ Generation: gw.Generation,
+ },
+ Spec: gw.Spec.DeepCopy(),
+ Status: gw.Status.DeepCopy(),
+ }
+}
+
func setGatewayConditions(
existing *gateway.GatewayStatus,
gen int64,
diff --git a/dubbod/discovery/pkg/config/kube/gateway/referencegrants.go
b/dubbod/discovery/pkg/config/kube/gateway/referencegrants.go
new file mode 100644
index 00000000..e71d46e8
--- /dev/null
+++ b/dubbod/discovery/pkg/config/kube/gateway/referencegrants.go
@@ -0,0 +1,187 @@
+// 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 gateway
+
+import (
+ "fmt"
+ "strings"
+
+ "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"
+ "github.com/apache/dubbo-kubernetes/pkg/kube/krt"
+ "k8s.io/apimachinery/pkg/types"
+ gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
+ gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
+)
+
+const (
+ kubernetesSecretPrefix = "kubernetes://"
+ kubernetesGatewaySecretPrefix = "kubernetes-gateway://"
+)
+
+type referenceGrantStore struct {
+ Collection krt.Collection[ReferenceGrant]
+ Index krt.Index[ReferencePair, ReferenceGrant]
+}
+
+type Reference struct {
+ Kind config.GroupVersionKind
+ Namespace gatewayv1.Namespace
+}
+
+func (r Reference) String() string {
+ return r.Kind.String() + "/" + string(r.Namespace)
+}
+
+type ReferencePair struct {
+ To, From Reference
+}
+
+func (p ReferencePair) String() string {
+ return fmt.Sprintf("%s->%s", p.From, p.To)
+}
+
+type ReferenceGrant struct {
+ Source types.NamespacedName
+ From Reference
+ To Reference
+ AllowAll bool
+ AllowedName string
+}
+
+func (g ReferenceGrant) ResourceName() string {
+ nameKey := "*"
+ if !g.AllowAll {
+ nameKey = g.AllowedName
+ }
+ return g.Source.String() + "/" + g.From.Kind.String() + "/" +
string(g.From.Namespace) + "/" + g.To.Kind.String() + "/" +
string(g.To.Namespace) + "/" + nameKey
+}
+
+func ReferenceGrantsCollection(
+ referenceGrants krt.Collection[*gatewayv1beta1.ReferenceGrant],
+ opts krt.OptionsBuilder,
+) krt.Collection[ReferenceGrant] {
+ return krt.NewManyCollection(referenceGrants, func(ctx
krt.HandlerContext, obj *gatewayv1beta1.ReferenceGrant) []ReferenceGrant {
+ result := make([]ReferenceGrant, 0,
len(obj.Spec.From)*len(obj.Spec.To))
+ for _, from := range obj.Spec.From {
+ fromKind := normalizeReference(&from.Group, &from.Kind,
config.GroupVersionKind{})
+ if fromKind != gvk.KubernetesGateway && fromKind !=
gvk.HTTPRoute {
+ continue
+ }
+ fromRef := Reference{
+ Kind: fromKind,
+ Namespace: gatewayv1.Namespace(from.Namespace),
+ }
+ for _, to := range obj.Spec.To {
+ toKind := normalizeReference(&to.Group,
&to.Kind, config.GroupVersionKind{})
+ if toKind != gvk.Secret {
+ continue
+ }
+ grant := ReferenceGrant{
+ Source: types.NamespacedName{
+ Name: obj.Name,
+ Namespace: obj.Namespace,
+ },
+ From: fromRef,
+ To: Reference{
+ Kind: toKind,
+ Namespace:
gatewayv1.Namespace(obj.Namespace),
+ },
+ AllowAll: to.Name == nil,
+ }
+ if to.Name != nil {
+ grant.AllowedName = string(*to.Name)
+ }
+ result = append(result, grant)
+ }
+ }
+ return result
+ }, opts.WithName("ReferenceGrants")...)
+}
+
+func newReferenceGrantStore(collection krt.Collection[ReferenceGrant])
referenceGrantStore {
+ return referenceGrantStore{
+ Collection: collection,
+ Index: krt.NewIndex(collection, "toFrom", func(o
ReferenceGrant) []ReferencePair {
+ return []ReferencePair{{To: o.To, From: o.From}}
+ }),
+ }
+}
+
+func (refs referenceGrantStore) SecretAllowed(ctx krt.HandlerContext, kind
config.GroupVersionKind, resourceName string, namespace string) bool {
+ if refs.Collection == nil {
+ return false
+ }
+ name, targetNamespace, targetKind, ok :=
parseSecretResourceName(resourceName, namespace)
+ if !ok {
+ return false
+ }
+ pair := ReferencePair{
+ From: Reference{Kind: kind, Namespace:
gatewayv1.Namespace(namespace)},
+ To: Reference{Kind: targetKind, Namespace:
gatewayv1.Namespace(targetNamespace)},
+ }
+ for _, grant := range krt.FetchOrList(ctx, refs.Collection,
krt.FilterIndex(refs.Index, pair)) {
+ if grant.AllowAll || grant.AllowedName == name {
+ return true
+ }
+ }
+ return false
+}
+
+func parseSecretResourceName(resourceName string, defaultNamespace string)
(name string, namespace string, kind config.GroupVersionKind, ok bool) {
+ kind = gvk.Secret
+ switch {
+ case strings.HasPrefix(resourceName, kubernetesGatewaySecretPrefix):
+ parts := strings.Split(strings.TrimPrefix(resourceName,
kubernetesGatewaySecretPrefix), "/")
+ if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
+ return "", "", config.GroupVersionKind{}, false
+ }
+ return parts[1], parts[0], kind, true
+ case strings.HasPrefix(resourceName, kubernetesSecretPrefix):
+ parts := strings.Split(strings.TrimPrefix(resourceName,
kubernetesSecretPrefix), "/")
+ if len(parts) == 1 && parts[0] != "" {
+ return parts[0], defaultNamespace, kind, true
+ }
+ if len(parts) == 2 && parts[0] != "" && parts[1] != "" {
+ return parts[1], parts[0], kind, true
+ }
+ return "", "", config.GroupVersionKind{}, false
+ default:
+ parts := strings.Split(resourceName, "/")
+ if len(parts) == 1 && parts[0] != "" {
+ return parts[0], defaultNamespace, kind, true
+ }
+ if len(parts) == 2 && parts[0] != "" && parts[1] != "" {
+ return parts[1], parts[0], kind, true
+ }
+ return "", "", config.GroupVersionKind{}, false
+ }
+}
+
+func normalizeReference[G ~string, K ~string](group *G, kind *K, fallback
config.GroupVersionKind) config.GroupVersionKind {
+ out := fallback
+ if group != nil {
+ out.Group = string(*group)
+ }
+ if kind != nil {
+ out.Kind = string(*kind)
+ }
+ if schema, found := collections.All.FindByGroupKind(out); found {
+ return schema.GroupVersionKind()
+ }
+ return out
+}
diff --git a/dubbod/discovery/pkg/model/context.go
b/dubbod/discovery/pkg/model/context.go
index 52e41281..d0c30453 100644
--- a/dubbod/discovery/pkg/model/context.go
+++ b/dubbod/discovery/pkg/model/context.go
@@ -29,6 +29,7 @@ import (
networkutil
"github.com/apache/dubbo-kubernetes/dubbod/discovery/pkg/util/network"
"github.com/apache/dubbo-kubernetes/dubbod/discovery/pkg/features"
+ "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/mesh"
@@ -100,6 +101,7 @@ type Environment struct {
type GatewayController interface {
ConfigStoreController
Reconcile(ctx *PushContext)
+ SecretAllowed(ourKind config.GroupVersionKind, resourceName string,
namespace string) bool
}
func NewEnvironment() *Environment {
diff --git a/pkg/config/schema/collections/collections.gen.go
b/pkg/config/schema/collections/collections.gen.go
index c454cc1a..7f317175 100755
--- a/pkg/config/schema/collections/collections.gen.go
+++ b/pkg/config/schema/collections/collections.gen.go
@@ -24,6 +24,7 @@ import (
k8sioapipolicyv1 "k8s.io/api/policy/v1"
k8sioapiextensionsapiserverpkgapisapiextensionsv1
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
sigsk8siogatewayapiapisv1 "sigs.k8s.io/gateway-api/apis/v1"
+ sigsk8siogatewayapiapisv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
)
var (
@@ -354,6 +355,24 @@ var (
ValidateProto: validation.EmptyValidate,
}.MustBuild()
+ ReferenceGrant = resource.Builder{
+ Identifier: "ReferenceGrant",
+ Group: "gateway.networking.k8s.io",
+ Kind: "ReferenceGrant",
+ Plural: "referencegrants",
+ Version: "v1beta1",
+ VersionAliases: []string{
+ "v1beta1",
+ },
+ Proto:
"k8s.io.gateway_api.api.v1alpha1.ReferenceGrantSpec",
+ ReflectType:
reflect.TypeOf(&sigsk8siogatewayapiapisv1beta1.ReferenceGrantSpec{}).Elem(),
+ ProtoPackage: "sigs.k8s.io/gateway-api/apis/v1beta1",
+ ClusterScoped: false,
+ Synthetic: false,
+ Builtin: false,
+ ValidateProto: validation.EmptyValidate,
+ }.MustBuild()
+
RequestAuthentication = resource.Builder{
Identifier: "RequestAuthentication",
Group: "security.dubbo.apache.org",
@@ -467,6 +486,7 @@ var (
MustAdd(PeerAuthentication).
MustAdd(Pod).
MustAdd(PodDisruptionBudget).
+ MustAdd(ReferenceGrant).
MustAdd(RequestAuthentication).
MustAdd(Secret).
MustAdd(Service).
@@ -494,6 +514,7 @@ var (
MustAdd(Node).
MustAdd(Pod).
MustAdd(PodDisruptionBudget).
+ MustAdd(ReferenceGrant).
MustAdd(Secret).
MustAdd(Service).
MustAdd(ServiceAccount).
@@ -518,6 +539,7 @@ var (
MustAdd(HTTPRoute).
MustAdd(KubernetesGateway).
MustAdd(PeerAuthentication).
+ MustAdd(ReferenceGrant).
MustAdd(RequestAuthentication).
Build()
@@ -530,6 +552,7 @@ var (
MustAdd(HTTPRoute).
MustAdd(KubernetesGateway).
MustAdd(PeerAuthentication).
+ MustAdd(ReferenceGrant).
MustAdd(RequestAuthentication).
Build()
)
diff --git a/pkg/config/schema/gvk/resources.gen.go
b/pkg/config/schema/gvk/resources.gen.go
index 5e3768ba..eae934d9 100755
--- a/pkg/config/schema/gvk/resources.gen.go
+++ b/pkg/config/schema/gvk/resources.gen.go
@@ -35,6 +35,8 @@ var (
PeerAuthentication = config.GroupVersionKind{Group:
"security.dubbo.apache.org", Version: "v1alpha3", Kind: "PeerAuthentication"}
Pod = config.GroupVersionKind{Group: "",
Version: "v1", Kind: "Pod"}
PodDisruptionBudget = config.GroupVersionKind{Group:
"policy", Version: "v1", Kind: "PodDisruptionBudget"}
+ ReferenceGrant = config.GroupVersionKind{Group:
"gateway.networking.k8s.io", Version: "v1beta1", Kind: "ReferenceGrant"}
+ ReferenceGrant_v1beta1 = config.GroupVersionKind{Group:
"gateway.networking.k8s.io", Version: "v1beta1", Kind: "ReferenceGrant"}
RequestAuthentication = config.GroupVersionKind{Group:
"security.dubbo.apache.org", Version: "v1alpha3", Kind: "RequestAuthentication"}
Secret = config.GroupVersionKind{Group: "",
Version: "v1", Kind: "Secret"}
Service = config.GroupVersionKind{Group: "",
Version: "v1", Kind: "Service"}
@@ -96,6 +98,10 @@ func ToGVR(g config.GroupVersionKind)
(schema.GroupVersionResource, bool) {
return gvr.Pod, true
case PodDisruptionBudget:
return gvr.PodDisruptionBudget, true
+ case ReferenceGrant:
+ return gvr.ReferenceGrant, true
+ case ReferenceGrant_v1beta1:
+ return gvr.ReferenceGrant_v1beta1, true
case RequestAuthentication:
return gvr.RequestAuthentication, true
case Secret:
@@ -157,6 +163,8 @@ func MustToKind(g config.GroupVersionKind) kind.Kind {
return kind.Pod
case PodDisruptionBudget:
return kind.PodDisruptionBudget
+ case ReferenceGrant:
+ return kind.ReferenceGrant
case RequestAuthentication:
return kind.RequestAuthentication
case Secret:
@@ -229,6 +237,8 @@ func FromGVR(g schema.GroupVersionResource)
(config.GroupVersionKind, bool) {
return Pod, true
case gvr.PodDisruptionBudget:
return PodDisruptionBudget, true
+ case gvr.ReferenceGrant:
+ return ReferenceGrant, true
case gvr.RequestAuthentication:
return RequestAuthentication, true
case gvr.Secret:
diff --git a/pkg/config/schema/gvr/resources.gen.go
b/pkg/config/schema/gvr/resources.gen.go
index 21955199..a37e5a65 100755
--- a/pkg/config/schema/gvr/resources.gen.go
+++ b/pkg/config/schema/gvr/resources.gen.go
@@ -30,6 +30,8 @@ var (
PeerAuthentication = schema.GroupVersionResource{Group:
"security.dubbo.apache.org", Version: "v1alpha3", Resource:
"peerauthentications"}
Pod = schema.GroupVersionResource{Group: "",
Version: "v1", Resource: "pods"}
PodDisruptionBudget = schema.GroupVersionResource{Group:
"policy", Version: "v1", Resource: "poddisruptionbudgets"}
+ ReferenceGrant = schema.GroupVersionResource{Group:
"gateway.networking.k8s.io", Version: "v1beta1", Resource: "referencegrants"}
+ ReferenceGrant_v1beta1 = schema.GroupVersionResource{Group:
"gateway.networking.k8s.io", Version: "v1beta1", Resource: "referencegrants"}
RequestAuthentication = schema.GroupVersionResource{Group:
"security.dubbo.apache.org", Version: "v1alpha3", Resource:
"requestauthentications"}
Secret = schema.GroupVersionResource{Group: "",
Version: "v1", Resource: "secrets"}
Service = schema.GroupVersionResource{Group: "",
Version: "v1", Resource: "services"}
@@ -88,6 +90,10 @@ func IsClusterScoped(g schema.GroupVersionResource) bool {
return false
case PodDisruptionBudget:
return false
+ case ReferenceGrant:
+ return false
+ case ReferenceGrant_v1beta1:
+ return false
case RequestAuthentication:
return false
case Secret:
diff --git a/pkg/config/schema/kind/resources.gen.go
b/pkg/config/schema/kind/resources.gen.go
index 40ab69ab..268c1cf0 100755
--- a/pkg/config/schema/kind/resources.gen.go
+++ b/pkg/config/schema/kind/resources.gen.go
@@ -27,6 +27,7 @@ const (
PeerAuthentication
Pod
PodDisruptionBudget
+ ReferenceGrant
RequestAuthentication
Secret
Service
@@ -83,6 +84,8 @@ func (k Kind) String() string {
return "Pod"
case PodDisruptionBudget:
return "PodDisruptionBudget"
+ case ReferenceGrant:
+ return "ReferenceGrant"
case RequestAuthentication:
return "RequestAuthentication"
case Secret:
@@ -148,6 +151,8 @@ func FromString(s string) Kind {
return Pod
case "PodDisruptionBudget":
return PodDisruptionBudget
+ case "ReferenceGrant":
+ return ReferenceGrant
case "RequestAuthentication":
return RequestAuthentication
case "Secret":
diff --git a/pkg/config/schema/kubeclient/resources.gen.go
b/pkg/config/schema/kubeclient/resources.gen.go
index c571b09b..fbf1bf6e 100755
--- a/pkg/config/schema/kubeclient/resources.gen.go
+++ b/pkg/config/schema/kubeclient/resources.gen.go
@@ -28,6 +28,7 @@ import (
k8sioapipolicyv1 "k8s.io/api/policy/v1"
k8sioapiextensionsapiserverpkgapisapiextensionsv1
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
sigsk8siogatewayapiapisv1 "sigs.k8s.io/gateway-api/apis/v1"
+ sigsk8siogatewayapiapisv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
)
func GetWriteClient[T runtime.Object](c ClientGetter, namespace string)
ktypes.WriteAPI[T] {
@@ -72,6 +73,8 @@ func GetWriteClient[T runtime.Object](c ClientGetter,
namespace string) ktypes.W
return c.Kube().CoreV1().Pods(namespace).(ktypes.WriteAPI[T])
case *k8sioapipolicyv1.PodDisruptionBudget:
return
c.Kube().PolicyV1().PodDisruptionBudgets(namespace).(ktypes.WriteAPI[T])
+ case *sigsk8siogatewayapiapisv1beta1.ReferenceGrant:
+ return
c.GatewayAPI().GatewayV1beta1().ReferenceGrants(namespace).(ktypes.WriteAPI[T])
case
*apigithubcomapachedubbokubernetesapisecurityv1alpha3.RequestAuthentication:
return
c.Dubbo().SecurityV1alpha3().RequestAuthentications(namespace).(ktypes.WriteAPI[T])
case *k8sioapicorev1.Secret:
@@ -131,6 +134,8 @@ func GetClient[T, TL runtime.Object](c ClientGetter,
namespace string) ktypes.Re
return
c.Kube().CoreV1().Pods(namespace).(ktypes.ReadWriteAPI[T, TL])
case *k8sioapipolicyv1.PodDisruptionBudget:
return
c.Kube().PolicyV1().PodDisruptionBudgets(namespace).(ktypes.ReadWriteAPI[T, TL])
+ case *sigsk8siogatewayapiapisv1beta1.ReferenceGrant:
+ return
c.GatewayAPI().GatewayV1beta1().ReferenceGrants(namespace).(ktypes.ReadWriteAPI[T,
TL])
case
*apigithubcomapachedubbokubernetesapisecurityv1alpha3.RequestAuthentication:
return
c.Dubbo().SecurityV1alpha3().RequestAuthentications(namespace).(ktypes.ReadWriteAPI[T,
TL])
case *k8sioapicorev1.Secret:
@@ -190,6 +195,8 @@ func gvrToObject(g schema.GroupVersionResource)
runtime.Object {
return &k8sioapicorev1.Pod{}
case gvr.PodDisruptionBudget:
return &k8sioapipolicyv1.PodDisruptionBudget{}
+ case gvr.ReferenceGrant:
+ return &sigsk8siogatewayapiapisv1beta1.ReferenceGrant{}
case gvr.RequestAuthentication:
return
&apigithubcomapachedubbokubernetesapisecurityv1alpha3.RequestAuthentication{}
case gvr.Secret:
@@ -352,6 +359,13 @@ func getInformerFiltered(c ClientGetter, opts
ktypes.InformerOptions, g schema.G
w = func(options metav1.ListOptions) (watch.Interface, error) {
return
c.Kube().PolicyV1().PodDisruptionBudgets(opts.Namespace).Watch(context.Background(),
options)
}
+ case gvr.ReferenceGrant:
+ l = func(options metav1.ListOptions) (runtime.Object, error) {
+ return
c.GatewayAPI().GatewayV1beta1().ReferenceGrants(opts.Namespace).List(context.Background(),
options)
+ }
+ w = func(options metav1.ListOptions) (watch.Interface, error) {
+ return
c.GatewayAPI().GatewayV1beta1().ReferenceGrants(opts.Namespace).Watch(context.Background(),
options)
+ }
case gvr.RequestAuthentication:
l = func(options metav1.ListOptions) (runtime.Object, error) {
return
c.Dubbo().SecurityV1alpha3().RequestAuthentications(opts.Namespace).List(context.Background(),
options)
diff --git a/pkg/config/schema/kubetypes/resources.gen.go
b/pkg/config/schema/kubetypes/resources.gen.go
index 38722483..01799d04 100755
--- a/pkg/config/schema/kubetypes/resources.gen.go
+++ b/pkg/config/schema/kubetypes/resources.gen.go
@@ -19,6 +19,7 @@ import (
k8sioapipolicyv1 "k8s.io/api/policy/v1"
k8sioapiextensionsapiserverpkgapisapiextensionsv1
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
sigsk8siogatewayapiapisv1 "sigs.k8s.io/gateway-api/apis/v1"
+ sigsk8siogatewayapiapisv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
)
func getGvk(obj any) (config.GroupVersionKind, bool) {
@@ -71,6 +72,8 @@ func getGvk(obj any) (config.GroupVersionKind, bool) {
return gvk.Pod, true
case *k8sioapipolicyv1.PodDisruptionBudget:
return gvk.PodDisruptionBudget, true
+ case *sigsk8siogatewayapiapisv1beta1.ReferenceGrant:
+ return gvk.ReferenceGrant, true
case *githubcomkdubboapisecurityv1alpha3.RequestAuthentication:
return gvk.RequestAuthentication, true
case
*apigithubcomapachedubbokubernetesapisecurityv1alpha3.RequestAuthentication:
diff --git a/pkg/config/schema/metadata.yaml b/pkg/config/schema/metadata.yaml
index 5359af4a..a68dec0d 100644
--- a/pkg/config/schema/metadata.yaml
+++ b/pkg/config/schema/metadata.yaml
@@ -202,6 +202,15 @@ resources:
statusProto: "k8s.io.gateway_api.api.v1alpha1.PolicyStatus"
statusProtoPackage: "sigs.k8s.io/gateway-api/apis/v1"
+ - kind: "ReferenceGrant"
+ plural: "referencegrants"
+ group: "gateway.networking.k8s.io"
+ version: "v1beta1"
+ versionAliases:
+ - "v1beta1"
+ protoPackage: "sigs.k8s.io/gateway-api/apis/v1beta1"
+ proto: "k8s.io.gateway_api.api.v1alpha1.ReferenceGrantSpec"
+
## Dubbo resources
- kind: AuthorizationPolicy
plural: "authorizationpolicies"