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 58913c3f Update the delta xDS framework (#923)
58913c3f is described below

commit 58913c3fa01cf47fb9a5a1c3d40a56d667de19d4
Author: mfordjody <[email protected]>
AuthorDate: Thu Jun 25 19:18:02 2026 +0800

    Update the delta xDS framework (#923)
---
 .../discovery/pkg/networking/grpcgen/delta_test.go |  94 ++++++++++
 dubbod/discovery/pkg/networking/grpcgen/grpcgen.go |  61 +++++++
 dubbod/discovery/pkg/xds/delta.go                  |  15 +-
 dubbod/discovery/pkg/xds/delta_test.go             | 199 +++++++++++++++++++++
 dubbod/discovery/pkg/xds/xdsgen.go                 |   8 +
 pkg/cni/config.go                                  |   2 +-
 pkg/cni/install.go                                 |  16 +-
 7 files changed, 386 insertions(+), 9 deletions(-)

diff --git a/dubbod/discovery/pkg/networking/grpcgen/delta_test.go 
b/dubbod/discovery/pkg/networking/grpcgen/delta_test.go
new file mode 100644
index 00000000..f14db62c
--- /dev/null
+++ b/dubbod/discovery/pkg/networking/grpcgen/delta_test.go
@@ -0,0 +1,94 @@
+//
+// 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 grpcgen
+
+import (
+       "testing"
+
+       "github.com/apache/dubbo-kubernetes/dubbod/discovery/pkg/model"
+       v1 "github.com/apache/dubbo-kubernetes/dubbod/discovery/pkg/xds/v1"
+       "github.com/apache/dubbo-kubernetes/pkg/config/host"
+       "github.com/apache/dubbo-kubernetes/pkg/config/schema/kind"
+       "github.com/apache/dubbo-kubernetes/pkg/util/sets"
+)
+
+func TestGenerateDeltasBuildsOnlyChangedServiceClusters(t *testing.T) {
+       nginxHost := "nginx.app.svc.cluster.local"
+       apiHost := "api.app.svc.cluster.local"
+       nginxCluster := model.BuildSubsetKey(model.TrafficDirectionOutbound, 
"", host.Name(nginxHost), 80)
+       apiCluster := model.BuildSubsetKey(model.TrafficDirectionOutbound, "", 
host.Name(apiHost), 80)
+       push := newRDSTestPushContext(t, nil, []*model.Service{
+               newRDSTestService("nginx", "app", nginxHost, 80),
+               newRDSTestService("api", "app", apiHost, 80),
+       })
+
+       resources, removed, details, usedDelta, err := 
(&GrpcConfigGenerator{}).GenerateDeltas(newMTLSTestProxy(), &model.PushRequest{
+               Push:           push,
+               Full:           true,
+               ConfigsUpdated: sets.New(model.ConfigKey{Kind: kind.Service, 
Name: nginxHost, Namespace: "app"}),
+       }, &model.WatchedResource{
+               TypeUrl:       v1.ClusterType,
+               ResourceNames: sets.New(nginxCluster, apiCluster),
+       })
+       if err != nil {
+               t.Fatalf("GenerateDeltas() failed: %v", err)
+       }
+       if !usedDelta || !details.Incremental {
+               t.Fatalf("usedDelta/details.Incremental = %v/%v, want 
true/true", usedDelta, details.Incremental)
+       }
+       if got := resourceNamesForTest(resources); len(got) != 1 || got[0] != 
nginxCluster {
+               t.Fatalf("resources = %v, want [%s]", got, nginxCluster)
+       }
+       if len(removed) != 0 {
+               t.Fatalf("removed = %v, want empty", removed)
+       }
+}
+
+func TestGenerateDeltasRemovesClustersForDeletedService(t *testing.T) {
+       nginxHost := "nginx.app.svc.cluster.local"
+       nginxCluster := model.BuildSubsetKey(model.TrafficDirectionOutbound, 
"", host.Name(nginxHost), 80)
+       push := newRDSTestPushContext(t, nil, nil)
+
+       resources, removed, _, usedDelta, err := 
(&GrpcConfigGenerator{}).GenerateDeltas(newMTLSTestProxy(), &model.PushRequest{
+               Push:           push,
+               Full:           true,
+               ConfigsUpdated: sets.New(model.ConfigKey{Kind: kind.Service, 
Name: nginxHost, Namespace: "app"}),
+       }, &model.WatchedResource{
+               TypeUrl:       v1.ClusterType,
+               ResourceNames: sets.New(nginxCluster),
+       })
+       if err != nil {
+               t.Fatalf("GenerateDeltas() failed: %v", err)
+       }
+       if !usedDelta {
+               t.Fatal("usedDelta = false, want true")
+       }
+       if len(resources) != 0 {
+               t.Fatalf("resources = %v, want empty", 
resourceNamesForTest(resources))
+       }
+       if len(removed) != 1 || removed[0] != nginxCluster {
+               t.Fatalf("removed = %v, want [%s]", removed, nginxCluster)
+       }
+}
+
+func resourceNamesForTest(resources model.Resources) []string {
+       out := make([]string, 0, len(resources))
+       for _, resource := range resources {
+               out = append(out, resource.Name)
+       }
+       return out
+}
diff --git a/dubbod/discovery/pkg/networking/grpcgen/grpcgen.go 
b/dubbod/discovery/pkg/networking/grpcgen/grpcgen.go
index 9b35f745..bbd44ad7 100644
--- a/dubbod/discovery/pkg/networking/grpcgen/grpcgen.go
+++ b/dubbod/discovery/pkg/networking/grpcgen/grpcgen.go
@@ -19,7 +19,9 @@ package grpcgen
 import (
        "github.com/apache/dubbo-kubernetes/dubbod/discovery/pkg/model"
        v1 "github.com/apache/dubbo-kubernetes/dubbod/discovery/pkg/xds/v1"
+       "github.com/apache/dubbo-kubernetes/pkg/config/schema/kind"
        dubbolog "github.com/apache/dubbo-kubernetes/pkg/log"
+       "github.com/apache/dubbo-kubernetes/pkg/util/sets"
        tlsv1 "github.com/kdubbo/xds-api/extensions/transport_sockets/tls/v1"
 )
 
@@ -27,6 +29,8 @@ var log = dubbolog.RegisterScope("grpcgen", "xDS Generator 
for Proxyless gRPC")
 
 type GrpcConfigGenerator struct{}
 
+var _ model.XdsDeltaResourceGenerator = &GrpcConfigGenerator{}
+
 func (g *GrpcConfigGenerator) Generate(proxy *model.Proxy, w 
*model.WatchedResource, req *model.PushRequest) (model.Resources, 
model.XdsLogDetails, error) {
        // Extract requested resource names from WatchedResource
        // If ResourceNames is empty (wildcard request), pass empty slice
@@ -52,6 +56,63 @@ func (g *GrpcConfigGenerator) Generate(proxy *model.Proxy, w 
*model.WatchedResou
        return nil, model.DefaultXdsLogDetails, nil
 }
 
+func (g *GrpcConfigGenerator) GenerateDeltas(proxy *model.Proxy, req 
*model.PushRequest, w *model.WatchedResource) (model.Resources, 
model.DeletedResources, model.XdsLogDetails, bool, error) {
+       if w == nil {
+               return nil, nil, model.DefaultXdsLogDetails, false, nil
+       }
+       if w.TypeUrl != v1.ClusterType || !grpcCanUseDeltaClusters(req) {
+               resources, logDetails, err := g.Generate(proxy, w, req)
+               return resources, nil, logDetails, false, err
+       }
+
+       changedNames := grpcDeltaClusterNames(w, req)
+       if len(changedNames) == 0 {
+               return nil, nil, model.DefaultXdsLogDetails, true, nil
+       }
+       resources := g.BuildClusters(proxy, req.Push, 
sets.SortedList(changedNames))
+
+       built := sets.NewWithLength[string](len(resources))
+       for _, resource := range resources {
+               if resource != nil {
+                       built.Insert(resource.Name)
+               }
+       }
+       removed := changedNames.Copy()
+       for name := range built {
+               removed.Delete(name)
+       }
+
+       logDetails := model.DefaultXdsLogDetails
+       logDetails.Incremental = true
+       return resources, sets.SortedList(removed), logDetails, true, nil
+}
+
+func grpcCanUseDeltaClusters(req *model.PushRequest) bool {
+       if req == nil || req.Forced || len(req.ConfigsUpdated) == 0 {
+               return false
+       }
+       for cfg := range req.ConfigsUpdated {
+               if cfg.Kind != kind.Service || cfg.Name == "" {
+                       return false
+               }
+       }
+       return true
+}
+
+func grpcDeltaClusterNames(w *model.WatchedResource, req *model.PushRequest) 
sets.String {
+       updatedServices := sets.New[string]()
+       for cfg := range req.ConfigsUpdated {
+               updatedServices.Insert(cfg.Name)
+       }
+       changed := sets.New[string]()
+       for clusterName := range w.ResourceNames {
+               if 
updatedServices.Contains(model.ParseSubsetKeyHostname(clusterName)) {
+                       changed.Insert(clusterName)
+               }
+       }
+       return changed
+}
+
 // buildCommonTLSContext creates a TLS context that matches gRPC xDS 
expectations.
 // - Uses certificate provider "default" for workload certs and root CA
 // - Does not configure explicit SAN matches (left to future hardening)
diff --git a/dubbod/discovery/pkg/xds/delta.go 
b/dubbod/discovery/pkg/xds/delta.go
index 8647c770..bb305e6b 100644
--- a/dubbod/discovery/pkg/xds/delta.go
+++ b/dubbod/discovery/pkg/xds/delta.go
@@ -20,6 +20,7 @@ import (
        "errors"
        dubbolog "github.com/apache/dubbo-kubernetes/pkg/log"
        "strings"
+       "time"
 
        dubbogrpc "github.com/apache/dubbo-kubernetes/dubbod/discovery/pkg/grpc"
        "github.com/apache/dubbo-kubernetes/dubbod/discovery/pkg/model"
@@ -335,7 +336,19 @@ func (conn *Connection) sendDelta(res 
*discovery.DeltaDiscoveryResponse, newReso
                return conn.deltaStream.Send(res)
        }
        err := sendResonse()
-       if status.Convert(err).Code() == codes.DeadlineExceeded {
+       if err == nil {
+               conn.proxy.UpdateWatchedResource(res.TypeUrl, func(wr 
*model.WatchedResource) *model.WatchedResource {
+                       if wr == nil {
+                               wr = &model.WatchedResource{TypeUrl: 
res.TypeUrl}
+                       }
+                       if newResourceNames != nil {
+                               wr.ResourceNames = newResourceNames
+                       }
+                       wr.NonceSent = res.Nonce
+                       wr.LastSendTime = time.Now()
+                       return wr
+               })
+       } else if status.Convert(err).Code() == codes.DeadlineExceeded {
                deltaLog.Infof("Timeout writing %s: %v", conn.ID(), 
v1.GetShortType(res.TypeUrl))
        }
        return err
diff --git a/dubbod/discovery/pkg/xds/delta_test.go 
b/dubbod/discovery/pkg/xds/delta_test.go
new file mode 100644
index 00000000..992060a0
--- /dev/null
+++ b/dubbod/discovery/pkg/xds/delta_test.go
@@ -0,0 +1,199 @@
+//
+// 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 xds
+
+import (
+       "context"
+       "io"
+       "testing"
+
+       "github.com/apache/dubbo-kubernetes/dubbod/discovery/pkg/model"
+       "github.com/apache/dubbo-kubernetes/dubbod/discovery/pkg/util/protoconv"
+       v1 "github.com/apache/dubbo-kubernetes/dubbod/discovery/pkg/xds/v1"
+       "github.com/apache/dubbo-kubernetes/pkg/util/sets"
+       cluster "github.com/kdubbo/xds-api/cluster/v1"
+       discovery "github.com/kdubbo/xds-api/service/discovery/v1"
+       "google.golang.org/grpc/metadata"
+)
+
+func TestPushDeltaXdsFiltersSubscribedResources(t *testing.T) {
+       oldName := "outbound|80||old.app.svc.cluster.local"
+       newName := "outbound|80||new.app.svc.cluster.local"
+       server, con, stream := newDeltaXDSTestServer(filteredDeltaGenerator{})
+       con.proxy.NewWatchedResource(v1.ClusterType, []string{oldName, newName})
+
+       err := server.pushDeltaXds(con, 
con.proxy.GetWatchedResource(v1.ClusterType), &model.PushRequest{
+               Push: con.proxy.LastPushContext,
+               Delta: model.ResourceDelta{
+                       Subscribed: sets.New(newName),
+               },
+       })
+       if err != nil {
+               t.Fatalf("pushDeltaXds() failed: %v", err)
+       }
+
+       resp := stream.takeDeltaResponse(t)
+       if got := resourceNames(resp.Resources); len(got) != 1 || got[0] != 
newName {
+               t.Fatalf("resources = %v, want [%s]", got, newName)
+       }
+       assertWatchedNames(t, con.proxy.GetWatchedResource(v1.ClusterType), 
newName)
+       if con.proxy.GetWatchedResource(v1.ClusterType).NonceSent == "" {
+               t.Fatal("NonceSent was not recorded")
+       }
+}
+
+func TestPushDeltaXdsAppliesRemovedResourcesToWatchedState(t *testing.T) {
+       removedName := "outbound|80||removed.app.svc.cluster.local"
+       keptName := "outbound|80||kept.app.svc.cluster.local"
+       addedName := "outbound|80||added.app.svc.cluster.local"
+       server, con, stream := newDeltaXDSTestServer(staticDeltaGenerator{
+               resources: model.Resources{clusterResource(addedName)},
+               removed:   []string{removedName},
+       })
+       con.proxy.NewWatchedResource(v1.ClusterType, []string{removedName, 
keptName})
+
+       err := server.pushDeltaXds(con, 
con.proxy.GetWatchedResource(v1.ClusterType), &model.PushRequest{
+               Push: con.proxy.LastPushContext,
+       })
+       if err != nil {
+               t.Fatalf("pushDeltaXds() failed: %v", err)
+       }
+
+       resp := stream.takeDeltaResponse(t)
+       if got := resp.RemovedResources; len(got) != 1 || got[0] != removedName 
{
+               t.Fatalf("removed = %v, want [%s]", got, removedName)
+       }
+       assertWatchedNames(t, con.proxy.GetWatchedResource(v1.ClusterType), 
keptName, addedName)
+}
+
+func newDeltaXDSTestServer(generator model.XdsResourceGenerator) 
(*DiscoveryServer, *Connection, *fakeDeltaADSStream) {
+       push := model.NewPushContext()
+       push.PushVersion = "test-version"
+       server := NewDiscoveryServer(model.NewEnvironment(), nil, nil)
+       server.Generators = map[string]model.XdsResourceGenerator{
+               v1.ClusterType: generator,
+       }
+       stream := newFakeDeltaADSStream()
+       con := newDeltaConnection("127.0.0.1:26010", stream)
+       con.s = server
+       con.proxy = &model.Proxy{
+               ID:               "pod-1",
+               Type:             model.Proxyless,
+               Metadata:         &model.NodeMetadata{Generator: "grpc", 
Namespace: "app"},
+               WatchedResources: map[string]*model.WatchedResource{},
+               LastPushContext:  push,
+       }
+       return server, con, stream
+}
+
+type filteredDeltaGenerator struct{}
+
+func (filteredDeltaGenerator) Generate(_ *model.Proxy, w 
*model.WatchedResource, _ *model.PushRequest) (model.Resources, 
model.XdsLogDetails, error) {
+       resources := make(model.Resources, 0, len(w.ResourceNames))
+       for name := range w.ResourceNames {
+               resources = append(resources, clusterResource(name))
+       }
+       return resources, model.DefaultXdsLogDetails, nil
+}
+
+type staticDeltaGenerator struct {
+       resources model.Resources
+       removed   []string
+}
+
+func (g staticDeltaGenerator) Generate(*model.Proxy, *model.WatchedResource, 
*model.PushRequest) (model.Resources, model.XdsLogDetails, error) {
+       return g.resources, model.DefaultXdsLogDetails, nil
+}
+
+func (g staticDeltaGenerator) GenerateDeltas(*model.Proxy, *model.PushRequest, 
*model.WatchedResource) (model.Resources, model.DeletedResources, 
model.XdsLogDetails, bool, error) {
+       return g.resources, g.removed, model.DefaultXdsLogDetails, true, nil
+}
+
+func clusterResource(name string) *discovery.Resource {
+       return &discovery.Resource{
+               Name:     name,
+               Resource: protoconv.MessageToAny(&cluster.Cluster{Name: name}),
+       }
+}
+
+func resourceNames(resources model.Resources) []string {
+       out := make([]string, 0, len(resources))
+       for _, resource := range resources {
+               out = append(out, resource.Name)
+       }
+       return out
+}
+
+type fakeDeltaADSStream struct {
+       ctx    context.Context
+       recvCh chan *discovery.DeltaDiscoveryRequest
+       sendCh chan *discovery.DeltaDiscoveryResponse
+}
+
+func newFakeDeltaADSStream() *fakeDeltaADSStream {
+       return &fakeDeltaADSStream{
+               ctx:    context.Background(),
+               recvCh: make(chan *discovery.DeltaDiscoveryRequest, 8),
+               sendCh: make(chan *discovery.DeltaDiscoveryResponse, 8),
+       }
+}
+
+func (f *fakeDeltaADSStream) Send(resp *discovery.DeltaDiscoveryResponse) 
error {
+       f.sendCh <- resp
+       return nil
+}
+
+func (f *fakeDeltaADSStream) Recv() (*discovery.DeltaDiscoveryRequest, error) {
+       req, ok := <-f.recvCh
+       if !ok {
+               return nil, io.EOF
+       }
+       return req, nil
+}
+
+func (f *fakeDeltaADSStream) SetHeader(metadata.MD) error {
+       return nil
+}
+
+func (f *fakeDeltaADSStream) SendHeader(metadata.MD) error {
+       return nil
+}
+
+func (f *fakeDeltaADSStream) SetTrailer(metadata.MD) {}
+
+func (f *fakeDeltaADSStream) Context() context.Context {
+       return f.ctx
+}
+
+func (f *fakeDeltaADSStream) SendMsg(any) error {
+       return nil
+}
+
+func (f *fakeDeltaADSStream) RecvMsg(any) error {
+       return nil
+}
+
+func (f *fakeDeltaADSStream) takeDeltaResponse(t *testing.T) 
*discovery.DeltaDiscoveryResponse {
+       t.Helper()
+       select {
+       case resp := <-f.sendCh:
+               return resp
+       default:
+               t.Fatal("expected delta response")
+               return nil
+       }
+}
diff --git a/dubbod/discovery/pkg/xds/xdsgen.go 
b/dubbod/discovery/pkg/xds/xdsgen.go
index 93262b60..ab9588cc 100644
--- a/dubbod/discovery/pkg/xds/xdsgen.go
+++ b/dubbod/discovery/pkg/xds/xdsgen.go
@@ -535,6 +535,14 @@ func (s *DiscoveryServer) pushDeltaXds(con *Connection, w 
*model.WatchedResource
        }
 
        var logFiltered string
+       if !req.Delta.IsEmpty() {
+               logFiltered = " filtered:" + 
strconv.Itoa(len(w.ResourceNames)-len(req.Delta.Subscribed))
+               w = &model.WatchedResource{
+                       TypeUrl:       w.TypeUrl,
+                       ResourceNames: req.Delta.Subscribed,
+               }
+       }
+
        var res model.Resources
        var deletedRes model.DeletedResources
        var logdata model.XdsLogDetails
diff --git a/pkg/cni/config.go b/pkg/cni/config.go
index eda915fd..82620bdb 100644
--- a/pkg/cni/config.go
+++ b/pkg/cni/config.go
@@ -26,7 +26,7 @@ import (
 )
 
 const (
-       defaultCNIVersion   = "1.0.0"
+       defaultCNIVersion   = "1.0.0" // 无用版本
        defaultStateDir     = "/var/run/dubbo-cni"
        defaultIPTablesPath = "iptables"
        defaultIPSetPath    = "ipset"
diff --git a/pkg/cni/install.go b/pkg/cni/install.go
index 86ae9829..df8bdb93 100644
--- a/pkg/cni/install.go
+++ b/pkg/cni/install.go
@@ -30,11 +30,13 @@ import (
 )
 
 const (
-       defaultCNIBinDir          = "/opt/cni/bin"
-       defaultCNIConfDir         = "/etc/cni/net.d"
-       defaultInstallerInterval  = time.Minute
-       defaultKubeConfigFileName = "dubbo-cni-kubeconfig"
-       installBackupSuffix       = ".dubbo-cni.bak"
+       defaultCNIBinDir               = "/opt/cni/bin"
+       defaultCNIConfDir              = "/etc/cni/net.d"
+       defaultServiceAccountTokenPath = 
"/var/run/secrets/kubernetes.io/serviceaccount/token"
+       defaultServiceAccountCAPath    = 
"/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
+       defaultInstallerInterval       = time.Minute
+       defaultKubeConfigFileName      = "dubbo-cni-kubeconfig"
+       installBackupSuffix            = ".dubbo-cni.bak"
 )
 
 type InstallerOptions struct {
@@ -60,8 +62,8 @@ func DefaultInstallerOptions() InstallerOptions {
                BinDir:                  defaultCNIBinDir,
                ConfDir:                 defaultCNIConfDir,
                StateDir:                defaultStateDir,
-               ServiceAccountTokenPath: 
"/var/run/secrets/kubernetes.io/serviceaccount/token",
-               ServiceAccountCAPath:    
"/var/run/secrets/kubernetes.io/serviceaccount/ca.crt",
+               ServiceAccountTokenPath: defaultServiceAccountTokenPath,
+               ServiceAccountCAPath:    defaultServiceAccountCAPath,
                ManagedLabel:            inject.ProxylessManagedLabel,
                ManagedLabelValue:       inject.ProxylessManagedLabelValue,
                IPTablesPath:            defaultIPTablesPath,

Reply via email to