From 2afbd8ef5321af60a2ba79d0a14f56425e3520b3 Mon Sep 17 00:00:00 2001
From: Martin Weindel <martin.weindel@sap.com>
Date: Thu, 5 Dec 2024 11:12:25 +0100
Subject: [PATCH 01/10] Remove creation of meta data records; changed logic to
 delete DNSEntries; cleanup of meta data records

---
 pkg/controller/provider/aws/execution.go      |   4 +-
 .../provider/azure-private/execution.go       |   4 +-
 pkg/controller/provider/azure/execution.go    |   4 +-
 pkg/controller/provider/google/execution.go   |   6 +-
 .../provider/openstack/execution.go           |   4 +-
 .../provider/openstack/handler_test.go        |  38 +--
 pkg/controller/provider/powerdns/execution.go |   3 +-
 pkg/controller/provider/rfc2136/execution.go  |   6 +-
 pkg/dns/dnsset.go                             | 139 +++--------
 pkg/dns/mapping.go                            |  79 ------
 pkg/dns/mapping_test.go                       |  51 ----
 pkg/dns/provider/changemodel.go               | 234 ++++++++----------
 pkg/dns/provider/const.go                     |   3 +-
 pkg/dns/provider/controller.go                |  18 +-
 pkg/dns/provider/entry.go                     |  38 +--
 pkg/dns/provider/errors/errors.go             |  12 -
 pkg/dns/provider/interface.go                 |  30 ++-
 pkg/dns/provider/ownercache.go                |  35 ---
 pkg/dns/provider/raw/execution.go             |   6 +-
 pkg/dns/provider/state.go                     |  40 +--
 pkg/dns/provider/state_entry.go               |  34 ++-
 pkg/dns/provider/state_owner.go               |  76 ------
 pkg/dns/provider/state_zone.go                |  65 +++--
 pkg/dns/provider/statistic/statistic.go       |  42 +---
 pkg/dns/provider/zone.go                      |  14 --
 pkg/dns/provider/zonecache.go                 |  18 +-
 pkg/dns/provider/zonetxn/change.go            | 110 ++++++++
 pkg/dns/provider/zonetxn/mapping.go           |  22 ++
 pkg/dns/records.go                            |   3 +-
 pkg/dns/records_test.go                       |  12 +-
 pkg/dns/utils/target.go                       |  19 --
 pkg/dns/utils/utils_dns.go                    |   1 -
 pkg/dns/validation.go                         |  14 --
 pkg/dns/validation_test.go                    |   5 +-
 pkg/server/metrics/metrics.go                 |  52 ----
 .../remote/conversion/conversion_test.go      |   4 +-
 test/integration/compound/compound_test.go    |  20 +-
 37 files changed, 429 insertions(+), 836 deletions(-)
 create mode 100644 pkg/dns/provider/zonetxn/change.go
 create mode 100644 pkg/dns/provider/zonetxn/mapping.go

diff --git a/pkg/controller/provider/aws/execution.go b/pkg/controller/provider/aws/execution.go
index 6a721374..4a8c947d 100644
--- a/pkg/controller/provider/aws/execution.go
+++ b/pkg/controller/provider/aws/execution.go
@@ -72,8 +72,8 @@ func buildResourceRecordSet(ctx context.Context, name dns.DNSSetName, policy *dn
 }
 
 func (this *Execution) addChange(ctx context.Context, action route53types.ChangeAction, req *provider.ChangeRequest, dnsset *dns.DNSSet) error {
-	name, rset := dns.MapToProvider(req.Type, dnsset, this.zone.Domain())
-	name = name.Align()
+	name := dnsset.Name.Align()
+	rset := dnsset.Sets[req.Type]
 	if len(rset.Records) == 0 {
 		return nil
 	}
diff --git a/pkg/controller/provider/azure-private/execution.go b/pkg/controller/provider/azure-private/execution.go
index 823aa9b0..8678840b 100644
--- a/pkg/controller/provider/azure-private/execution.go
+++ b/pkg/controller/provider/azure-private/execution.go
@@ -56,12 +56,12 @@ func (exec *Execution) buildRecordSet(req *provider.ChangeRequest) (buildStatus,
 		return bs_invalidRoutingPolicy, "", nil
 	}
 
-	setName, rset := dns.MapToProvider(req.Type, dnsset, exec.zoneName)
-	name, ok := utils.DropZoneName(setName.DNSName, exec.zoneName)
+	name, ok := utils.DropZoneName(dnsset.Name.DNSName, exec.zoneName)
 	if !ok {
 		return bs_invalidName, "", &armprivatedns.RecordSet{Name: &name}
 	}
 
+	rset := dnsset.Sets[req.Type]
 	if len(rset.Records) == 0 {
 		return bs_empty, "", nil
 	}
diff --git a/pkg/controller/provider/azure/execution.go b/pkg/controller/provider/azure/execution.go
index 16509002..52e2c7f6 100644
--- a/pkg/controller/provider/azure/execution.go
+++ b/pkg/controller/provider/azure/execution.go
@@ -56,12 +56,12 @@ func (exec *Execution) buildRecordSet(req *provider.ChangeRequest) (buildStatus,
 		return bs_invalidRoutingPolicy, "", nil
 	}
 
-	setName, rset := dns.MapToProvider(req.Type, dnsset, exec.zoneName)
-	name, ok := utils.DropZoneName(setName.DNSName, exec.zoneName)
+	name, ok := utils.DropZoneName(dnsset.Name.DNSName, exec.zoneName)
 	if !ok {
 		return bs_invalidName, "", &armdns.RecordSet{Name: &name}
 	}
 
+	rset := dnsset.Sets[req.Type]
 	if len(rset.Records) == 0 {
 		return bs_empty, "", nil
 	}
diff --git a/pkg/controller/provider/google/execution.go b/pkg/controller/provider/google/execution.go
index c8997195..855ff5ae 100644
--- a/pkg/controller/provider/google/execution.go
+++ b/pkg/controller/provider/google/execution.go
@@ -51,11 +51,13 @@ func (this *Execution) addChange(req *provider.ChangeRequest) {
 	var err error
 
 	if req.Addition != nil {
-		setName, newset = dns.MapToProvider(req.Type, req.Addition, this.zone.Domain())
+		setName = req.Addition.Name
+		newset = req.Addition.Sets[req.Type]
 		policy, err = extractRoutingPolicy(req.Addition)
 	}
 	if req.Deletion != nil {
-		setName, oldset = dns.MapToProvider(req.Type, req.Deletion, this.zone.Domain())
+		setName = req.Deletion.Name
+		oldset = req.Deletion.Sets[req.Type]
 		if req.Addition == nil {
 			policy, err = extractRoutingPolicy(req.Deletion)
 		}
diff --git a/pkg/controller/provider/openstack/execution.go b/pkg/controller/provider/openstack/execution.go
index de6a0813..2c833186 100644
--- a/pkg/controller/provider/openstack/execution.go
+++ b/pkg/controller/provider/openstack/execution.go
@@ -52,8 +52,8 @@ func (exec *Execution) buildRecordSet(req *provider.ChangeRequest) (buildStatus,
 		return bsInvalidRoutingPolicy, nil
 	}
 
-	name, rset := dns.MapToProvider(req.Type, dnsset, exec.zone.Domain())
-
+	name := dnsset.Name
+	rset := dnsset.Sets[req.Type]
 	if len(rset.Records) == 0 {
 		return bsEmpty, nil
 	}
diff --git a/pkg/controller/provider/openstack/handler_test.go b/pkg/controller/provider/openstack/handler_test.go
index 47d1c45d..7d427fa6 100644
--- a/pkg/controller/provider/openstack/handler_test.go
+++ b/pkg/controller/provider/openstack/handler_test.go
@@ -268,24 +268,12 @@ func TestGetZoneStateAndExecuteRequests(t *testing.T) {
 			Type:    "A",
 			Records: []string{"1.2.3.4", "5.6.7.8"},
 		},
-		{
-			Name:    "comment-sub1.z1.test.",
-			TTL:     600,
-			Type:    "TXT",
-			Records: []string{"\"owner=test\"", "\"prefix=comment-\""},
-		},
 		{
 			Name:    "sub2.z1.test.",
 			TTL:     302,
 			Type:    "CNAME",
 			Records: []string{"cname.target.test."},
 		},
-		{
-			Name:    "comment-sub2.z1.test.",
-			TTL:     600,
-			Type:    "TXT",
-			Records: []string{"\"owner=test\"", "\"prefix=comment-\""},
-		},
 		{
 			Name:    "sub3.z1.test.",
 			TTL:     303,
@@ -298,7 +286,6 @@ func TestGetZoneStateAndExecuteRequests(t *testing.T) {
 		Ω(err).ShouldNot(HaveOccurred(), fmt.Sprintf("CreateRecordSet failed for %s %s", opts.Name, opts.Type))
 	}
 
-	stdMeta := buildRecordSet("META", 600, "\"owner=test\"", "\"prefix=comment-\"")
 	sub1 := dns.DNSSetName{DNSName: "sub1.z1.test"}
 	sub2 := dns.DNSSetName{DNSName: "sub2.z1.test"}
 	sub3 := dns.DNSSetName{DNSName: "sub3.z1.test"}
@@ -306,15 +293,13 @@ func TestGetZoneStateAndExecuteRequests(t *testing.T) {
 		sub1: &dns.DNSSet{
 			Name: sub1,
 			Sets: dns.RecordSets{
-				"A":    buildRecordSet("A", 301, "1.2.3.4", "5.6.7.8"),
-				"META": stdMeta,
+				"A": buildRecordSet("A", 301, "1.2.3.4", "5.6.7.8"),
 			},
 		},
 		sub2: &dns.DNSSet{
 			Name: sub2,
 			Sets: dns.RecordSets{
 				"CNAME": buildRecordSet("CNAME", 302, "cname.target.test"),
-				"META":  stdMeta,
 			},
 		},
 		dns.DNSSetName{DNSName: "sub3.z1.test"}: &dns.DNSSet{
@@ -343,16 +328,6 @@ func TestGetZoneStateAndExecuteRequests(t *testing.T) {
 				},
 			},
 		},
-		{
-			Action: provider.R_CREATE,
-			Type:   "META",
-			Addition: &dns.DNSSet{
-				Name: sub4,
-				Sets: dns.RecordSets{
-					"META": stdMeta,
-				},
-			},
-		},
 		{
 			Action: provider.R_UPDATE,
 			Type:   "A",
@@ -368,11 +343,6 @@ func TestGetZoneStateAndExecuteRequests(t *testing.T) {
 			Type:     "CNAME",
 			Deletion: expectedDnssets[sub2],
 		},
-		{
-			Action:   provider.R_DELETE,
-			Type:     "META",
-			Deletion: expectedDnssets[sub2],
-		},
 		{
 			Action:   provider.R_DELETE,
 			Type:     "TXT",
@@ -386,15 +356,13 @@ func TestGetZoneStateAndExecuteRequests(t *testing.T) {
 		sub1: &dns.DNSSet{
 			Name: sub1,
 			Sets: dns.RecordSets{
-				"A":    buildRecordSet("A", 305, "1.2.3.55", "5.6.7.8"),
-				"META": stdMeta,
+				"A": buildRecordSet("A", 305, "1.2.3.55", "5.6.7.8"),
 			},
 		},
 		sub4: &dns.DNSSet{
 			Name: sub4,
 			Sets: dns.RecordSets{
-				"A":    buildRecordSet("A", 304, "11.22.33.44"),
-				"META": stdMeta,
+				"A": buildRecordSet("A", 304, "11.22.33.44"),
 			},
 		},
 	}
diff --git a/pkg/controller/provider/powerdns/execution.go b/pkg/controller/provider/powerdns/execution.go
index bc758aa4..26950dda 100644
--- a/pkg/controller/provider/powerdns/execution.go
+++ b/pkg/controller/provider/powerdns/execution.go
@@ -42,7 +42,8 @@ func (exec *Execution) buildRecordSet(req *provider.ChangeRequest) (*RecordSet,
 		dnsset = req.Deletion
 	}
 
-	name, rset := dns.MapToProvider(req.Type, dnsset, exec.zone.Domain())
+	name := dnsset.Name
+	rset := dnsset.Sets[req.Type]
 
 	if name.SetIdentifier != "" || dnsset.RoutingPolicy != nil {
 		return nil, fmt.Errorf("routing policies not supported for " + TYPE_CODE)
diff --git a/pkg/controller/provider/rfc2136/execution.go b/pkg/controller/provider/rfc2136/execution.go
index 0d7eea7b..89099c36 100644
--- a/pkg/controller/provider/rfc2136/execution.go
+++ b/pkg/controller/provider/rfc2136/execution.go
@@ -39,10 +39,12 @@ func (exec *Execution) buildRecordSet(req *provider.ChangeRequest) ([]miekgdns.R
 
 	domain := dns.NormalizeHostname(exec.handler.zone)
 	if req.Addition != nil {
-		setName, addset = dns.MapToProvider(req.Type, req.Addition, domain)
+		setName = req.Addition.Name
+		addset = req.Addition.Sets[req.Type]
 	}
 	if req.Deletion != nil {
-		setName, delset = dns.MapToProvider(req.Type, req.Deletion, domain)
+		setName = req.Deletion.Name
+		delset = req.Deletion.Sets[req.Type]
 	}
 	if setName.DNSName == "" || (addset.Length() == 0 && delset.Length() == 0) {
 		return nil, nil, nil
diff --git a/pkg/dns/dnsset.go b/pkg/dns/dnsset.go
index 3ad87e3b..fce6c92c 100644
--- a/pkg/dns/dnsset.go
+++ b/pkg/dns/dnsset.go
@@ -8,40 +8,20 @@ import (
 	"reflect"
 
 	"github.com/gardener/controller-manager-library/pkg/utils"
-
-	api "github.com/gardener/external-dns-management/pkg/apis/dns/v1alpha1"
 )
 
 ////////////////////////////////////////////////////////////////////////////////
-// A DNSSet contains Record sets for an DNS name. The name is given without
+// A DNSSet contains record sets for an DNS name. The name is given without
 // trailing dot. If the provider required this dot, it must be removed or addeed
 // whe reading or writing recordsets, respectively.
 // Supported record set types are:
 // - TXT
 // - CNAME
 // - A
-// - META   virtual type used by this API (see below) to store meta data
+// - AAAA
 //
 // If multiple CNAME records are given they will be mapped to A records
-// by resolving the cnames. THis resolution will be updated periodically,
-//
-// The META records contain attribute settings of the form "<attr>=<value>".
-// They are used to store the identifier of the controller and other
-// meta data to identity sets maintained or owned by this controller.
-// This record set must be stored and restored by the provider in some
-//  applicable way.
-//
-// This library supports a default mechanics for ths task, that can be used by
-// the provider:
-// This record set always contains a prefix attribute used to map META
-// records to TXT records finally stored by the provider.
-// Because not all regular record types can be combined with TXT records
-// the META text records are stores for a separate dns name composed of
-// the prefix and the original name.
-// This mapping is done by the the two functions MapFromProvider and
-// MapToProvider. These methods can be called by the provider when reading
-// or writing a record set, respectively. The map the given set to
-// an effective set and dns name for the desired purpose.
+// by resolving the cnames. This resolution will be updated periodically.
 
 type DNSSets map[DNSSetName]*DNSSet
 
@@ -55,10 +35,7 @@ func (dnssets DNSSets) AddRecordSetFromProvider(dnsName string, rs *RecordSet) {
 }
 
 func (dnssets DNSSets) AddRecordSetFromProviderEx(setName DNSSetName, policy *RoutingPolicy, rs *RecordSet) {
-	name := setName.Normalize()
-	name, rs = MapFromProvider(name, rs)
-
-	dnssets.AddRecordSet(name, policy, rs)
+	dnssets.AddRecordSet(setName.Normalize(), policy, rs)
 }
 
 func (dnssets DNSSets) AddRecordSet(name DNSSetName, policy *RoutingPolicy, rs *RecordSet) {
@@ -94,30 +71,8 @@ func (dnssets DNSSets) Clone() DNSSets {
 	return clone
 }
 
-// GetOwners returns all owners for all DNSSets
-func (dnssets DNSSets) GetOwners() utils.StringSet {
-	owners := utils.NewStringSet()
-	for _, dnsset := range dnssets {
-		o := dnsset.GetMetaAttr(ATTR_OWNER)
-		if o != "" {
-			owners.Add(o)
-		}
-	}
-	return owners
-}
-
-const (
-	ATTR_OWNER  = "owner"
-	ATTR_PREFIX = "prefix"
-	ATTR_KIND   = "kind"
-
-	ATTR_TIMESTAMP = "ts"
-	ATTR_LOCKID    = "lockid"
-)
-
 type DNSSet struct {
 	Name          DNSSetName
-	Kind          string
 	UpdateGroup   string
 	Sets          RecordSets
 	RoutingPolicy *RoutingPolicy
@@ -125,7 +80,7 @@ type DNSSet struct {
 
 func (this *DNSSet) Clone() *DNSSet {
 	return &DNSSet{
-		Name: this.Name, Sets: this.Sets.Clone(), UpdateGroup: this.UpdateGroup, Kind: this.Kind,
+		Name: this.Name, Sets: this.Sets.Clone(), UpdateGroup: this.UpdateGroup,
 		RoutingPolicy: this.RoutingPolicy.Clone(),
 	}
 }
@@ -167,59 +122,6 @@ func (this *DNSSet) DeleteTxtAttr(name string) {
 	this.deleteAttr(RS_TXT, name)
 }
 
-func (this *DNSSet) GetMetaAttr(name string) string {
-	return this.getAttr(RS_META, name)
-}
-
-func (this *DNSSet) SetMetaAttr(name string, value string) {
-	this.setAttr(RS_META, name, value)
-}
-
-func (this *DNSSet) DeleteMetaAttr(name string) {
-	this.deleteAttr(RS_META, name)
-}
-
-func (this *DNSSet) IsOwnedBy(ownership Ownership) bool {
-	o := this.GetMetaAttr(ATTR_OWNER)
-	return o != "" && ownership.IsResponsibleFor(o)
-}
-
-func (this *DNSSet) IsForeign(ownership Ownership) bool {
-	o := this.GetMetaAttr(ATTR_OWNER)
-	return o != "" && !ownership.IsResponsibleFor(o)
-}
-
-func (this *DNSSet) GetOwner() string {
-	return this.GetMetaAttr(ATTR_OWNER)
-}
-
-func (this *DNSSet) SetOwner(ownerid string) *DNSSet {
-	this.SetMetaAttr(ATTR_OWNER, ownerid)
-	return this
-}
-
-func (this *DNSSet) GetKind() string {
-	if this.Kind == "" {
-		this.Kind = this.GetMetaAttr(ATTR_KIND)
-	}
-	if this.Kind == "" {
-		this.Kind = api.DNSEntryKind
-	}
-	return this.Kind
-}
-
-func (this *DNSSet) SetKind(t string, prop ...bool) *DNSSet {
-	this.Kind = t
-	if t != api.DNSEntryKind {
-		if len(prop) == 0 || prop[0] {
-			this.SetMetaAttr(ATTR_KIND, t)
-		}
-	} else {
-		this.DeleteMetaAttr(ATTR_KIND)
-	}
-	return this
-}
-
 func (this *DNSSet) SetRecordSet(rtype string, ttl int64, values ...string) {
 	records := make([]*Record, len(values))
 	for i, r := range values {
@@ -232,8 +134,17 @@ func NewDNSSet(name DNSSetName, routingPolicy *RoutingPolicy) *DNSSet {
 	return &DNSSet{Name: name, RoutingPolicy: routingPolicy, Sets: map[string]*RecordSet{}}
 }
 
+// Match matches DNSSet equality
+func (this *DNSSet) Match(that *DNSSet) bool {
+	return this.match(that, nil)
+}
+
 // MatchRecordTypeSubset matches DNSSet equality for given record type subset.
 func (this *DNSSet) MatchRecordTypeSubset(that *DNSSet, rtype string) bool {
+	return this.match(that, &rtype)
+}
+
+func (this *DNSSet) match(that *DNSSet, restrictToRecordType *string) bool {
 	if this == that {
 		return true
 	}
@@ -249,13 +160,25 @@ func (this *DNSSet) MatchRecordTypeSubset(that *DNSSet, rtype string) bool {
 	if this.RoutingPolicy != that.RoutingPolicy && !reflect.DeepEqual(this.RoutingPolicy, that.RoutingPolicy) {
 		return false
 	}
-	rs1 := this.Sets[rtype]
-	rs2 := that.Sets[rtype]
-	if (rs1 == nil) != (rs2 == nil) {
-		return false
+	if restrictToRecordType != nil {
+		rs1, rs2 := this.Sets[*restrictToRecordType], that.Sets[*restrictToRecordType]
+		if rs1 != nil && rs2 != nil {
+			return rs1.Match(rs2)
+		}
+		return rs1 == nil && rs2 == nil
 	}
-	if rs1 != nil && !rs1.Match(rs2) {
+
+	if len(this.Sets) != len(that.Sets) {
 		return false
 	}
+	for k, v := range this.Sets {
+		w := that.Sets[k]
+		if w == nil {
+			return false
+		}
+		if !v.Match(w) {
+			return false
+		}
+	}
 	return true
 }
diff --git a/pkg/dns/mapping.go b/pkg/dns/mapping.go
index 9f98cd4e..f6188c39 100644
--- a/pkg/dns/mapping.go
+++ b/pkg/dns/mapping.go
@@ -8,12 +8,6 @@ import (
 	"strings"
 )
 
-////////////////////////////////////////////////////////////////////////////////
-// Text Record ObjectName Mapping
-////////////////////////////////////////////////////////////////////////////////
-
-var TxtPrefix = "comment-"
-
 func AlignHostname(host string) string {
 	if strings.HasSuffix(host, ".") {
 		return host
@@ -30,76 +24,3 @@ func NormalizeHostname(host string) string {
 	}
 	return host
 }
-
-func MapToProvider(rtype string, dnsset *DNSSet, base string) (DNSSetName, *RecordSet) {
-	dnsName := dnsset.Name.DNSName
-	rs := dnsset.Sets[rtype]
-	if rtype == RS_META {
-		prefix := dnsset.GetMetaAttr(ATTR_PREFIX)
-		if prefix == "" {
-			prefix = TxtPrefix
-			dnsset.SetMetaAttr(ATTR_PREFIX, prefix)
-		}
-		metaName := calcMetaRecordDomainName(dnsName, prefix, base)
-		new := *dnsset.Sets[rtype]
-		new.Type = RS_TXT
-		return dnsset.Name.WithDNSName(metaName), &new
-	}
-	return dnsset.Name, rs
-}
-
-func calcMetaRecordDomainName(name, prefix, base string) string {
-	add := ""
-	if name == base {
-		prefix += "-base."
-	} else if strings.HasPrefix(name, "*.") {
-		add = "*."
-		name = name[2:]
-		if name == base {
-			prefix += "-base."
-		}
-	} else if strings.HasPrefix(name, "@.") {
-		// special case: allow apex label for Azure
-		name = name[2:]
-		prefix += "---at."
-	}
-	return add + prefix + name
-}
-
-// CalcMetaRecordDomainNameForValidation returns domain name of metadata TXT DNS record if globally defined prefix is used.
-// As it does not consider the zone, it may be wrong for the zone base domain.
-func CalcMetaRecordDomainNameForValidation(name string) string {
-	return calcMetaRecordDomainName(name, TxtPrefix, "")
-}
-
-func MapFromProvider(name DNSSetName, rs *RecordSet) (DNSSetName, *RecordSet) {
-	dns := name.DNSName
-	if rs.Type == RS_TXT {
-		prefix := rs.GetAttr(ATTR_PREFIX)
-		if prefix != "" {
-			add := ""
-			if strings.HasPrefix(dns, "*.") {
-				add = "*."
-				dns = dns[2:]
-			}
-			if strings.HasPrefix(dns, prefix) {
-				new := *rs
-				new.Type = RS_META
-				dns = dns[len(prefix):]
-				if strings.HasPrefix(dns, "-base.") {
-					dns = dns[6:]
-				} else if strings.HasPrefix(dns, "---at.") {
-					dns = dns[6:]
-					add = "@."
-				} else {
-					// for backwards compatibility of form *.comment-.basedomain
-					dns = strings.TrimPrefix(dns, ".")
-				}
-				return name.WithDNSName(add + dns), &new
-			} else {
-				return name.WithDNSName(add + dns), rs
-			}
-		}
-	}
-	return name.WithDNSName(dns), rs
-}
diff --git a/pkg/dns/mapping_test.go b/pkg/dns/mapping_test.go
index ea7e7bf1..853986b6 100644
--- a/pkg/dns/mapping_test.go
+++ b/pkg/dns/mapping_test.go
@@ -6,8 +6,6 @@ package dns
 
 import (
 	"testing"
-
-	. "github.com/onsi/gomega"
 )
 
 func TestAlignHostname(t *testing.T) {
@@ -36,52 +34,3 @@ func TestNormalizeHostname(t *testing.T) {
 		}
 	}
 }
-
-func TestMapToFromProvider(t *testing.T) {
-	RegisterTestingT(t)
-
-	table := []struct {
-		domainName          string
-		hasOwnCommentRecord bool
-		wantedName          string
-	}{
-		{"a.myzone.de", false, "comment-a.myzone.de"},
-		{"a.myzone.de", true, "mycomment-a.myzone.de"},
-		{"*.a.myzone.de", false, "*.comment-a.myzone.de"},
-		{"*.myzone.de", false, "*.comment--base.myzone.de"},
-		{"@.myzone.de", false, "comment----at.myzone.de"},
-		{"myzone.de", false, "comment--base.myzone.de"},
-	}
-
-	rtype := RS_META
-	base := "myzone.de"
-
-	for _, entry := range table {
-		inputRecords := Records{&Record{"\"owner=test\""}}
-		var wantedRecords Records
-		if entry.hasOwnCommentRecord {
-			inputRecords = append(inputRecords, &Record{"\"prefix=mycomment-\""})
-			wantedRecords = inputRecords
-		} else {
-			wantedRecords = append(inputRecords, &Record{"\"prefix=comment-\""})
-		}
-		dnsset := DNSSet{
-			Name: DNSSetName{DNSName: entry.domainName},
-			Sets: RecordSets{RS_META: &RecordSet{Type: RS_META, TTL: 600, Records: inputRecords}},
-		}
-
-		actualName, actualRecordSet := MapToProvider(rtype, &dnsset, base)
-
-		Ω(actualName).Should(Equal(DNSSetName{DNSName: entry.wantedName}), "Name should match")
-		Ω(actualRecordSet.Type).Should(Equal(RS_TXT), "Type mismatch")
-		Ω(actualRecordSet.TTL).Should(Equal(int64(600)), "TTL mismatch")
-		Ω(actualRecordSet.Records).Should(Equal(wantedRecords))
-
-		reversedName, reversedRecordSet := MapFromProvider(actualName, actualRecordSet)
-
-		Ω(reversedName).Should(Equal(DNSSetName{DNSName: entry.domainName}), "Reversed name should match")
-		Ω(reversedRecordSet.Type).Should(Equal(RS_META), "Reversed RecordSet.Type should match")
-		Ω(reversedRecordSet.TTL).Should(Equal(int64(600)), "TTL mismatch")
-		Ω(reversedRecordSet.Records).Should(Equal(wantedRecords))
-	}
-}
diff --git a/pkg/dns/provider/changemodel.go b/pkg/dns/provider/changemodel.go
index 36380edc..204b249c 100644
--- a/pkg/dns/provider/changemodel.go
+++ b/pkg/dns/provider/changemodel.go
@@ -7,11 +7,12 @@ package provider
 import (
 	"fmt"
 	"reflect"
+	"strconv"
+	"strings"
 	"time"
 
 	api "github.com/gardener/external-dns-management/pkg/apis/dns/v1alpha1"
 	"github.com/gardener/external-dns-management/pkg/dns"
-	perrs "github.com/gardener/external-dns-management/pkg/dns/provider/errors"
 	dnsutils "github.com/gardener/external-dns-management/pkg/dns/utils"
 
 	"github.com/gardener/controller-manager-library/pkg/logger"
@@ -88,6 +89,8 @@ type ChangeGroup struct {
 	requests      ChangeRequests
 	model         *ChangeModel
 	providerCount int
+
+	cleanedMetadataRecords int
 }
 
 func newChangeGroup(name string, provider DNSProvider, model *ChangeModel) *ChangeGroup {
@@ -99,41 +102,48 @@ func (this *ChangeGroup) cleanup(logger logger.LogContext, model *ChangeModel) b
 	for _, s := range this.dnssets {
 		_, ok := model.applied[s.Name]
 		if !ok {
-			if s.IsOwnedBy(model.ownership) {
-				if model.ExistsInEquivalentZone(s.Name) {
-					continue
+			if model.ExistsInEquivalentZone(s.Name) {
+				continue
+			}
+			if e := model.IsStale(ZonedDNSSetName{ZoneID: model.ZoneId(), DNSSetName: s.Name}); e != nil {
+				if e.IsDeleting() {
+					model.failedDNSNames.Add(s.Name) // preventing deletion of stale entry
 				}
-				if e := model.IsStale(ZonedDNSSetName{ZoneID: model.ZoneId(), DNSSetName: s.Name}); e != nil {
-					if e.IsDeleting() {
-						model.failedDNSNames.Add(s.Name) // preventing deletion of stale entry
-					}
-					status := e.Object().Status()
-					msg := MSG_PRESERVED
-					trigger := false
-					if status.State == api.STATE_ERROR || status.State == api.STATE_INVALID {
-						msg = msg + ": " + utils.StringValue(status.Message)
-						model.Infof("found stale set '%s': %s -> preserve unchanged", utils.StringValue(status.Message), s.Name)
-					} else {
-						model.Infof("found stale set '%s' -> preserve unchanged", s.Name)
-						trigger = true
-					}
-					upd, err := e.UpdateStatus(logger, api.STATE_STALE, msg)
-					if trigger && (!upd || err != nil) {
-						e.Trigger(logger)
-					}
+				status := e.Object().Status()
+				msg := MSG_PRESERVED
+				trigger := false
+				if status.State == api.STATE_ERROR || status.State == api.STATE_INVALID {
+					msg = msg + ": " + utils.StringValue(status.Message)
+					model.Infof("found stale set '%s': %s -> preserve unchanged", utils.StringValue(status.Message), s.Name)
 				} else {
-					model.Infof("found unapplied managed set '%s'", s.Name)
-					var done DoneHandler
-					for _, e := range model.context.entries {
-						if e.dnsSetName == s.Name {
-							done = NewStatusUpdate(logger, e, model.context.fhandler)
-							break
-						}
+					model.Infof("found stale set '%s' -> preserve unchanged", s.Name)
+					trigger = true
+				}
+				upd, err := e.UpdateStatus(logger, api.STATE_STALE, msg)
+				if trigger && (!upd || err != nil) {
+					e.Trigger(logger)
+				}
+			} else {
+				oldSet := model.oldDNSSets[s.Name]
+				if oldSet == nil {
+					// not part of transaction, but old metadata entries may be present for cleanup
+					mod = this.partialCleanupOfMetadataRecords(logger, s) || mod
+					continue
+				}
+				model.Infof("found unapplied managed set '%s'", s.Name)
+				var done DoneHandler
+				for _, e := range model.context.entries {
+					if e.dnsSetName == s.Name {
+						done = NewStatusUpdate(logger, e, model.context.fhandler)
+						break
 					}
-					for ty := range s.Sets {
-						mod = true
-						this.addDeleteRequest(s, ty, model.wrappedDoneHandler(s.Name, done))
+				}
+				for ty := range s.Sets {
+					if _, ok := oldSet.Sets[ty]; !ok {
+						continue
 					}
+					mod = true
+					this.addDeleteRequest(s, ty, model.wrappedDoneHandler(s.Name, done))
 				}
 			}
 		}
@@ -141,6 +151,42 @@ func (this *ChangeGroup) cleanup(logger logger.LogContext, model *ChangeModel) b
 	return mod
 }
 
+func (this *ChangeGroup) partialCleanupOfMetadataRecords(logger logger.LogContext, s *dns.DNSSet) bool {
+	if this.cleanedMetadataRecords >= this.model.config.MaxMetadataRecordDeletionsPerReconciliation {
+		// Maximum number of metadata records to delete per reconciliation reached.
+		// To avoid excessive deletions, we stop here and continue in the next reconciliation
+		return false
+	}
+	if this.model.ownership == nil || len(this.model.ownership.GetIds()) == 0 {
+		// no known owners to clean up metadata records
+		return false
+	}
+
+	if set, ok := s.Sets[dns.RS_TXT]; ok {
+		name := s.Name.DNSName
+		for _, prefix := range []string{"comment-", "*.comment-"} {
+			if strings.HasPrefix(name, prefix) {
+				var foundPrefix, foundOwner bool
+				for _, r := range set.Records {
+					v, _ := strconv.Unquote(r.Value)
+					if strings.HasPrefix(v, "prefix=comment-") {
+						foundPrefix = true
+					} else if strings.HasPrefix(v, "owner=") && this.model.ownership.IsResponsibleFor(strings.TrimPrefix(v, "owner=")) {
+						foundOwner = true
+					}
+				}
+				if foundPrefix && foundOwner {
+					logger.Infof("cleaning up metadata record for %s", name)
+					this.cleanedMetadataRecords++
+					this.addDeleteRequest(s, dns.RS_TXT, nil)
+					return true
+				}
+			}
+		}
+	}
+	return false
+}
+
 func (this *ChangeGroup) update(logger logger.LogContext, model *ChangeModel) bool {
 	ok := true
 	model.Infof("reconcile entries for %s (with %d requests)", this.name, len(this.requests))
@@ -191,6 +237,7 @@ type ChangeModel struct {
 	providergroups map[string]*ChangeGroup
 	zonestate      DNSZoneState
 	failedDNSNames dns.DNSNameSet
+	oldDNSSets     dns.DNSSets
 }
 
 type ChangeResult struct {
@@ -199,7 +246,7 @@ type ChangeResult struct {
 	Error    error
 }
 
-func NewChangeModel(logger logger.LogContext, ownership dns.Ownership, req *zoneReconciliation, config Config) *ChangeModel {
+func NewChangeModel(logger logger.LogContext, ownership dns.Ownership, req *zoneReconciliation, config Config, oldDNSSets dns.DNSSets) *ChangeModel {
 	return &ChangeModel{
 		LogContext:     logger,
 		config:         config,
@@ -208,6 +255,7 @@ func NewChangeModel(logger logger.LogContext, ownership dns.Ownership, req *zone
 		applied:        map[dns.DNSSetName]*dns.DNSSet{},
 		providergroups: map[string]*ChangeGroup{},
 		failedDNSNames: dns.DNSNameSet{},
+		oldDNSSets:     oldDNSSets,
 	}
 }
 
@@ -267,7 +315,6 @@ func (this *ChangeModel) Setup() error {
 		return err
 	}
 	sets := this.zonestate.GetDNSSets()
-	this.context.zone.SetOwners(sets.GetOwners())
 	this.dangling = newChangeGroup("dangling entries", provider, this)
 	for setName, set := range sets {
 		var view *ChangeGroup
@@ -331,78 +378,54 @@ func (this *ChangeModel) Exec(apply bool, delete bool, name dns.DNSSetName, upda
 	oldset := view.dnssets[name]
 	newset := dns.NewDNSSet(name, spec.RoutingPolicy())
 	newset.UpdateGroup = updateGroup
-	newset.SetKind(spec.Kind())
 	if !delete {
-		this.ApplySpec(newset, oldset, p, spec)
+		this.ApplySpec(newset, p, spec)
 	}
 	mod := false
 	if oldset != nil {
-		this.Debugf("found old for %s %q", oldset.GetKind(), oldset.Name)
-		if this.IsForeign(oldset) {
-			err := &perrs.AlreadyBusyForOwner{Name: name, EntryCreatedAt: createdAt, Owner: oldset.GetOwner()}
-			retry := p.ReportZoneStateConflict(this.context.zone.getZone(), err)
-			if done != nil {
-				if apply && !retry {
-					done.SetInvalid(err)
+		this.Debugf("found old for entry %q", oldset.Name)
+		for ty, rset := range newset.Sets {
+			curset := oldset.Sets[ty]
+			if curset == nil {
+				if apply {
+					view.addCreateRequest(newset, ty, done)
 				}
+				mod = true
 			} else {
-				this.Warnf("no done handler and %s", err)
-			}
-			return ChangeResult{Error: err, Retry: retry}
-		} else {
-			if !spec.Responsible(oldset, this.ownership) {
-				return ChangeResult{}
-			}
-			if oldset.GetOwner() == "" && !this.Owns(oldset) {
-				if delete {
-					return ChangeResult{}
-				}
-				this.Infof("catch entry %q by reassigning owner", name)
-			}
-			for ty, rset := range newset.Sets {
-				curset := oldset.Sets[ty]
-				if curset == nil {
-					if apply {
-						view.addCreateRequest(newset, ty, done)
-					}
-					mod = true
-				} else {
-					olddns, _ := dns.MapToProvider(ty, oldset, this.Domain())
-					newdns, _ := dns.MapToProvider(ty, newset, this.Domain())
-					if olddns == newdns {
-						if !curset.Match(rset) || !reflect.DeepEqual(spec.RoutingPolicy(), oldset.RoutingPolicy) {
-							if apply {
-								view.addUpdateRequest(oldset, newset, ty, done)
-							}
-							mod = true
-						} else {
-							if apply {
-								this.Debugf("records type %s up to date for %s", ty, name)
-							}
+				olddns := oldset.Sets[ty]
+				newdns := newset.Sets[ty]
+				if olddns.Match(newdns) {
+					if !curset.Match(rset) || !reflect.DeepEqual(spec.RoutingPolicy(), oldset.RoutingPolicy) {
+						if apply {
+							view.addUpdateRequest(oldset, newset, ty, done)
 						}
+						mod = true
 					} else {
 						if apply {
-							view.addCreateRequest(newset, ty, done)
-							view.addDeleteRequest(oldset, ty, this.wrappedDoneHandler(name, nil))
+							this.Debugf("records type %s up to date for %s", ty, name)
 						}
-						mod = true
 					}
-				}
-			}
-			for ty := range oldset.Sets {
-				if _, ok := newset.Sets[ty]; !ok {
+				} else {
 					if apply {
-						view.addDeleteRequest(oldset, ty, done)
+						view.addCreateRequest(newset, ty, done)
+						view.addDeleteRequest(oldset, ty, this.wrappedDoneHandler(name, nil))
 					}
 					mod = true
 				}
 			}
 		}
+		for ty := range oldset.Sets {
+			if _, ok := newset.Sets[ty]; !ok {
+				if apply {
+					view.addDeleteRequest(oldset, ty, done)
+				}
+				mod = true
+			}
+		}
 	} else {
 		if !delete {
 			if apply {
 				this.Infof("no existing entry found for %s", name)
-				this.setOwner(newset, spec.OwnerId())
 				for ty := range newset.Sets {
 					view.addCreateRequest(newset, ty, done)
 				}
@@ -492,51 +515,14 @@ func (this *changeModelDoneHandler) Throttled() {
 /////////////////////////////////////////////////////////////////////////////////
 // DNSSets
 
-func (this *ChangeModel) Owns(set *dns.DNSSet) bool {
-	return set.IsOwnedBy(this.ownership)
-}
-
-func (this *ChangeModel) IsForeign(set *dns.DNSSet) bool {
-	return set.IsForeign(this.ownership)
-}
-
-func (this *ChangeModel) setOwner(set *dns.DNSSet, id string) bool {
-	if id == "" {
-		id = this.config.Ident
-	}
-	if id != "" {
-		set.SetOwner(id)
-		return true
-	}
-	return false
-}
-
-func (this *ChangeModel) ApplySpec(set *dns.DNSSet, base *dns.DNSSet, provider DNSProvider, spec TargetSpec) *dns.DNSSet {
-	set.SetKind(spec.Kind())
-	if base == nil || !this.IsForeign(base) {
-		if this.setOwner(set, spec.OwnerId()) {
-			set.SetMetaAttr(dns.ATTR_PREFIX, dns.TxtPrefix)
-		}
-	}
-
-	targetsets := set.Sets
+func (this *ChangeModel) ApplySpec(set *dns.DNSSet, provider DNSProvider, spec TargetSpec) *dns.DNSSet {
 	targets := provider.MapTargets(set.Name.DNSName, spec.Targets())
 	for _, t := range targets {
-		AddRecord(targetsets, t.GetRecordType(), t.GetHostName(), t.GetTTL())
+		set.Sets.AddRecord(t.GetRecordType(), t.GetHostName(), t.GetTTL())
 	}
-	set.Sets = targetsets
 	return set
 }
 
-func AddRecord(targetsets dns.RecordSets, ty string, host string, ttl int64) {
-	rs := targetsets[ty]
-	if rs == nil {
-		rs = dns.NewRecordSet(ty, ttl, nil)
-		targetsets[ty] = rs
-	}
-	rs.Records = append(rs.Records, &dns.Record{Value: host})
-}
-
 func atMost(s string, maxlen int) string {
 	if len(s) < maxlen {
 		return s
diff --git a/pkg/dns/provider/const.go b/pkg/dns/provider/const.go
index b1501a2a..69752100 100644
--- a/pkg/dns/provider/const.go
+++ b/pkg/dns/provider/const.go
@@ -26,6 +26,8 @@ const (
 	OPT_DISABLE_ZONE_STATE_CACHING = "disable-zone-state-caching"
 	OPT_DISABLE_DNSNAME_VALIDATION = "disable-dnsname-validation"
 
+	OPT_MAX_METADATA_RECORD_DELETIONS_PER_RECONCILIATION = "max-metadata-record-deletions-per-reconciliation"
+
 	OPT_REMOTE_ACCESS_PORT               = "remote-access-port"
 	OPT_REMOTE_ACCESS_CACERT             = "remote-access-cacert"
 	OPT_REMOTE_ACCESS_SERVER_SECRET_NAME = "remote-access-server-secret-name"
@@ -42,7 +44,6 @@ const (
 	OPT_ADVANCED_BLOCKED_ZONE = "blocked-zone"
 
 	CMD_HOSTEDZONE_PREFIX = "hostedzone:"
-	CMD_STATISTIC         = "statistic"
 
 	MSG_THROTTLING = "provider throttled"
 )
diff --git a/pkg/dns/provider/controller.go b/pkg/dns/provider/controller.go
index 81004999..d6e61bda 100644
--- a/pkg/dns/provider/controller.go
+++ b/pkg/dns/provider/controller.go
@@ -114,6 +114,7 @@ func DNSController(name string, factory DNSHandlerFactory) controller.Configurat
 		DefaultedStringOption(OPT_REMOTE_ACCESS_CACERT, "", "CA who signed client certs file").
 		DefaultedStringOption(OPT_REMOTE_ACCESS_SERVER_SECRET_NAME, "", "name of secret containing remote access server's certificate").
 		DefaultedStringOption(OPT_REMOTE_ACCESS_CLIENT_ID, "", "identifier used for remote access").
+		DefaultedIntOption(OPT_MAX_METADATA_RECORD_DELETIONS_PER_RECONCILIATION, 50, "maximum number of metadata owner records that can be deleted per zone reconciliation").
 		FinalizerDomain("dns.gardener.cloud").
 		Reconciler(DNSReconcilerType(factory)).
 		Cluster(TARGET_CLUSTER).
@@ -141,7 +142,6 @@ func DNSController(name string, factory DNSHandlerFactory) controller.Configurat
 			controller.NewResourceKey(api.GroupName, api.DNSHostedZonePolicyKind),
 		).
 		WorkerPool(DNS_POOL, 1, 15*time.Minute).CommandMatchers(utils.NewStringGlobMatcher(CMD_HOSTEDZONE_PREFIX+"*")).
-		WorkerPool("statistic", 2, 0).Commands(CMD_STATISTIC).
 		OptionSource(FACTORY_OPTIONS, FactoryOptionSourceCreator(factory))
 	return cfg
 }
@@ -211,16 +211,12 @@ func (this *reconciler) Start() {
 }
 
 func (this *reconciler) Command(logger logger.LogContext, cmd string) reconcile.Status {
-	switch cmd {
-	case CMD_STATISTIC:
-		this.state.UpdateOwnerCounts(logger)
-	default:
-		zoneid := this.state.DecodeZoneCommand(cmd)
-		if zoneid != nil {
-			return this.state.ReconcileZone(logger, *zoneid)
-		}
-		logger.Infof("got unhandled command %q", cmd)
+	zoneid := this.state.DecodeZoneCommand(cmd)
+	if zoneid != nil {
+		return this.state.ReconcileZone(logger, *zoneid)
 	}
+
+	logger.Warnf("got unhandled command %q", cmd)
 	return reconcile.Succeeded(logger)
 }
 
@@ -275,8 +271,6 @@ func (this *reconciler) Delete(logger logger.LogContext, obj resources.Object) r
 func (this *reconciler) Deleted(logger logger.LogContext, key resources.ClusterObjectKey) reconcile.Status {
 	logger.Debugf("deleted %s", key)
 	switch key.GroupKind() {
-	case ownerGroupKind:
-		return this.state.OwnerDeleted(logger, key)
 	case providerGroupKind:
 		return this.state.ProviderDeleted(logger, key.ObjectKey())
 	case entryGroupKind:
diff --git a/pkg/dns/provider/entry.go b/pkg/dns/provider/entry.go
index 0dba7b76..1820437c 100644
--- a/pkg/dns/provider/entry.go
+++ b/pkg/dns/provider/entry.go
@@ -24,6 +24,7 @@ import (
 	"github.com/gardener/external-dns-management/pkg/dns/provider/statistic"
 	dnsutils "github.com/gardener/external-dns-management/pkg/dns/utils"
 	"k8s.io/utils/ptr"
+	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/errors"
@@ -118,9 +119,6 @@ func (this *EntryVersion) RequiresUpdateFor(e *EntryVersion) (reasons []string)
 	if this.ZoneId() != e.ZoneId() {
 		reasons = append(reasons, "zone changed")
 	}
-	if this.OwnerId() != e.OwnerId() {
-		reasons = append(reasons, "ownerid changed")
-	}
 	if this.targets.DifferFrom(e.targets) {
 		reasons = append(reasons, "targets changed")
 	}
@@ -178,6 +176,10 @@ func (this *EntryVersion) ObjectName() resources.ObjectName {
 	return this.object.ObjectName()
 }
 
+func (this *EntryVersion) ObjectKey() client.ObjectKey {
+	return client.ObjectKey{Namespace: this.object.GetNamespace(), Name: this.object.GetName()}
+}
+
 func (this *EntryVersion) DNSName() string {
 	return this.dnsSetName.DNSName
 }
@@ -277,9 +279,6 @@ func complete(logger logger.LogContext, state *state, entry *dnsutils.DNSEntryOb
 		if entry.GetTTL() == nil {
 			newSpec.TTL = rspec.TTL
 		}
-		if entry.GetOwnerId() == nil {
-			newSpec.OwnerId = rspec.OwnerId
-		}
 		if entry.GetCNameLookupInterval() == nil {
 			newSpec.CNameLookupInterval = rspec.CNameLookupInterval
 		}
@@ -365,17 +364,6 @@ func validate(logger logger.LogContext, state *state, entry *EntryVersion, p *En
 	return
 }
 
-func validateOwner(_ logger.LogContext, state *state, entry *EntryVersion) error {
-	effspec := entry.object
-
-	if ownerid := utils.StringValue(effspec.GetOwnerId()); ownerid != "" {
-		if !state.ownerCache.IsResponsibleFor(ownerid) && !state.ownerCache.IsResponsiblePendingFor(ownerid) {
-			return fmt.Errorf("unknown owner id '%s'", ownerid)
-		}
-	}
-	return nil
-}
-
 func (this *EntryVersion) Setup(logger logger.LogContext, state *state, p *EntryPremise, op string, err error, config Config) reconcile.Status {
 	hello := dnsutils.NewLogMessage("%s ENTRY: %s, zoneid: %s, handler: %s, provider: %s, ref %+v", op, this.Object().Status().State, p.zoneid, p.ptype, Provider(p.provider), this.Object().GetReference())
 
@@ -463,13 +451,6 @@ func (this *EntryVersion) Setup(logger logger.LogContext, state *state, p *Entry
 
 	///////////// validate
 
-	if verr := validateOwner(logger, state, this); verr != nil {
-		hello.Infof(logger, "owner validation failed: %s", verr)
-
-		_, _ = this.UpdateStatus(logger, api.STATE_STALE, verr.Error())
-		return reconcile.Failed(logger, verr)
-	}
-
 	spec, targets, warnings, verr := validate(logger, state, this, p)
 	if verr != nil {
 		hello.Infof(logger, "validation failed: %s", verr)
@@ -789,14 +770,6 @@ func (this *Entry) Trigger(logger logger.LogContext) {
 	this.state.TriggerEntry(logger, this)
 }
 
-func (this *Entry) IsActive() bool {
-	id := this.OwnerId()
-	if id == "" {
-		id = this.state.config.Ident
-	}
-	return this.state.ownerCache.IsResponsibleFor(id)
-}
-
 func (this *Entry) IsModified() bool {
 	return this.modified
 }
@@ -853,7 +826,6 @@ func (this *Entry) updateStatistic(statistic *statistic.EntryStatistic) {
 		return
 	}
 	defer this.lock.Unlock()
-	statistic.Owners.Inc(this.OwnerId(), this.ProviderType(), this.ProviderName())
 	statistic.Providers.Inc(this.ProviderType(), this.ProviderName())
 }
 
diff --git a/pkg/dns/provider/errors/errors.go b/pkg/dns/provider/errors/errors.go
index c270051d..e33cc575 100644
--- a/pkg/dns/provider/errors/errors.go
+++ b/pkg/dns/provider/errors/errors.go
@@ -6,10 +6,8 @@ package errors
 
 import (
 	"fmt"
-	"time"
 
 	"github.com/gardener/controller-manager-library/pkg/resources"
-	"github.com/gardener/external-dns-management/pkg/dns"
 )
 
 type AlreadyBusyForEntry struct {
@@ -21,16 +19,6 @@ func (e *AlreadyBusyForEntry) Error() string {
 	return fmt.Sprintf("DNS name %q already busy for entry %q", e.DNSName, e.ObjectName)
 }
 
-type AlreadyBusyForOwner struct {
-	Name           dns.DNSSetName
-	EntryCreatedAt time.Time
-	Owner          string
-}
-
-func (e *AlreadyBusyForOwner) Error() string {
-	return fmt.Sprintf("DNS name %q already busy for owner %q", e.Name, e.Owner)
-}
-
 type NoSuchHostedZone struct {
 	ZoneId string
 	Err    error
diff --git a/pkg/dns/provider/interface.go b/pkg/dns/provider/interface.go
index 185a36aa..10666f9f 100644
--- a/pkg/dns/provider/interface.go
+++ b/pkg/dns/provider/interface.go
@@ -23,19 +23,20 @@ import (
 )
 
 type Config struct {
-	TTL                      int64
-	CacheTTL                 time.Duration
-	RescheduleDelay          time.Duration
-	StatusCheckPeriod        time.Duration
-	Ident                    string
-	Dryrun                   bool
-	ZoneStateCaching         bool
-	DisableDNSNameValidation bool
-	Delay                    time.Duration
-	EnabledTypes             utils.StringSet
-	Options                  *FactoryOptions
-	Factory                  DNSHandlerFactory
-	RemoteAccessConfig       *embed.RemoteAccessServerConfig
+	TTL                                         int64
+	CacheTTL                                    time.Duration
+	RescheduleDelay                             time.Duration
+	StatusCheckPeriod                           time.Duration
+	Ident                                       string
+	Dryrun                                      bool
+	ZoneStateCaching                            bool
+	DisableDNSNameValidation                    bool
+	Delay                                       time.Duration
+	EnabledTypes                                utils.StringSet
+	Options                                     *FactoryOptions
+	Factory                                     DNSHandlerFactory
+	RemoteAccessConfig                          *embed.RemoteAccessServerConfig
+	MaxMetadataRecordDeletionsPerReconciliation int
 }
 
 func NewConfigForController(c controller.Interface, factory DNSHandlerFactory) (*Config, error) {
@@ -80,6 +81,8 @@ func NewConfigForController(c controller.Interface, factory DNSHandlerFactory) (
 	disableZoneStateCaching, _ := c.GetBoolOption(OPT_DISABLE_ZONE_STATE_CACHING)
 	disableDNSNameValidation, _ := c.GetBoolOption(OPT_DISABLE_DNSNAME_VALIDATION)
 
+	maxMetadataRecordDeletionsPerReconciliation, _ := c.GetIntOption(OPT_MAX_METADATA_RECORD_DELETIONS_PER_RECONCILIATION)
+
 	enabled := utils.StringSet{}
 	types, err := c.GetStringOption(OPT_PROVIDERTYPES)
 	if err != nil || types == "" {
@@ -115,6 +118,7 @@ func NewConfigForController(c controller.Interface, factory DNSHandlerFactory) (
 		Options:                  fopts,
 		Factory:                  factory,
 		RemoteAccessConfig:       remoteAccessConfig,
+		MaxMetadataRecordDeletionsPerReconciliation: maxMetadataRecordDeletionsPerReconciliation,
 	}, nil
 }
 
diff --git a/pkg/dns/provider/ownercache.go b/pkg/dns/provider/ownercache.go
index c5f604cf..fd5dae25 100644
--- a/pkg/dns/provider/ownercache.go
+++ b/pkg/dns/provider/ownercache.go
@@ -11,7 +11,6 @@ import (
 	"github.com/gardener/controller-manager-library/pkg/resources"
 	"github.com/gardener/controller-manager-library/pkg/utils"
 	"github.com/gardener/external-dns-management/pkg/dns"
-	"github.com/gardener/external-dns-management/pkg/dns/provider/statistic"
 	dnsutils "github.com/gardener/external-dns-management/pkg/dns/utils"
 )
 
@@ -91,40 +90,6 @@ func (this *OwnerCache) GetIds() utils.StringSet {
 	return this.ownerids.KeySet()
 }
 
-func (this *OwnerCache) UpdateCountsWith(statistic statistic.OwnerStatistic, types utils.StringSet) OwnerCounts {
-	changed := OwnerCounts{}
-	this.lock.Lock()
-	defer this.lock.Unlock()
-	for id, e := range this.ownerids {
-		mod := false
-		pts := statistic.Get(id)
-		for t := range types {
-			c := 0
-			if v, ok := pts[t]; ok {
-				c = v.Count()
-			}
-			mod = mod || this.checkCount(&e, t, c)
-		}
-		if mod {
-			this.ownerids[id] = e
-			for n, o := range this.owners {
-				if o.id == id {
-					changed[n] = e.entrycounts
-				}
-			}
-		}
-	}
-	return changed
-}
-
-func (this *OwnerCache) checkCount(e *OwnerIDInfo, ptype string, count int) bool {
-	if e.entrycounts[ptype] != count {
-		e.entrycounts[ptype] = count
-		return true
-	}
-	return false
-}
-
 func (this *OwnerCache) UpdateOwner(owner *dnsutils.DNSOwnerObject) (changeset utils.StringSet, activeset utils.StringSet) {
 	active := owner.IsActive()
 	this.lock.Lock()
diff --git a/pkg/dns/provider/raw/execution.go b/pkg/dns/provider/raw/execution.go
index 7b12ad72..95cde193 100644
--- a/pkg/dns/provider/raw/execution.go
+++ b/pkg/dns/provider/raw/execution.go
@@ -60,10 +60,12 @@ func (this *Execution) AddChange(req *provider.ChangeRequest) {
 	var newset, oldset *dns.RecordSet
 
 	if req.Addition != nil {
-		name, newset = dns.MapToProvider(req.Type, req.Addition, this.domain)
+		name = req.Addition.Name
+		newset = req.Addition.Sets[req.Type]
 	}
 	if req.Deletion != nil {
-		name, oldset = dns.MapToProvider(req.Type, req.Deletion, this.domain)
+		name = req.Deletion.Name
+		oldset = req.Deletion.Sets[req.Type]
 	}
 	if name.DNSName == "" || (newset.Length() == 0 && oldset.Length() == 0) {
 		return
diff --git a/pkg/dns/provider/state.go b/pkg/dns/provider/state.go
index b3ad680b..c59baa4d 100644
--- a/pkg/dns/provider/state.go
+++ b/pkg/dns/provider/state.go
@@ -11,6 +11,7 @@ import (
 	"sync/atomic"
 	"time"
 
+	"github.com/gardener/external-dns-management/pkg/dns/provider/zonetxn"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/apimachinery/pkg/runtime"
@@ -103,7 +104,6 @@ type state struct {
 
 	context   ProviderContext
 	ownerresc resources.Interface
-	ownerupd  chan OwnerCounts
 
 	secretresc resources.Interface
 
@@ -144,6 +144,9 @@ type state struct {
 	lookupProcessor *lookupProcessor
 
 	providerEventListeners []ProviderEventListener
+
+	zoneTransactions     map[dns.ZoneID]*zonetxn.PendingTransaction
+	zoneTransactionsLock sync.Mutex
 }
 
 type rateLimiterData struct {
@@ -189,6 +192,7 @@ func NewDNSState(pctx ProviderContext, ownerresc, secretresc resources.Interface
 		providersecrets:     map[resources.ObjectName]resources.ObjectName{},
 		zonePolicies:        map[string]*dnsHostedZonePolicy{},
 		entries:             Entries{},
+		zoneTransactions:    map[dns.ZoneID]*zonetxn.PendingTransaction{},
 		outdated:            newSynchronizedEntries(),
 		blockingEntries:     map[resources.ObjectName]time.Time{},
 		dnsnames:            map[ZonedDNSSetName]*Entry{},
@@ -208,7 +212,6 @@ func (this *state) Setup() error {
 	}
 	this.zoneStates = newZoneStates(this.CreateStateTTLGetter(*syncPeriod))
 	this.dnsTicker = NewTicker(this.context.GetPool(DNS_POOL).Tick)
-	this.ownerupd = startOwnerUpdater(this.context, this.ownerresc)
 	processors, err := this.context.GetIntOption(OPT_SETUP)
 	if err != nil || processors <= 0 {
 		processors = 5
@@ -261,7 +264,6 @@ func (this *state) Setup() error {
 		return err
 	}
 
-	this.triggerStatistic()
 	this.initialized = true
 	this.context.Infof("setup done - starting reconciliation")
 	return nil
@@ -473,18 +475,6 @@ func (this *state) GetZonesForProvider(name resources.ObjectName) dnsHostedZones
 	return copyZones(this.providerzones[name])
 }
 
-func (this *state) GetEntriesForZone(logger logger.LogContext, zoneid dns.ZoneID) (Entries, ZonedDNSSetNames, bool) {
-	this.lock.RLock()
-	defer this.lock.RUnlock()
-	entries := Entries{}
-	zone := this.zones[zoneid]
-	if zone != nil {
-		entries, _, stale, deleting := this.addEntriesForZone(logger, entries, ZonedDNSSetNames{}, zone)
-		return entries, stale, deleting
-	}
-	return entries, nil, false
-}
-
 func (this *state) addEntriesForZone(
 	logger logger.LogContext,
 	entries Entries,
@@ -503,7 +493,7 @@ func (this *state) addEntriesForZone(
 		stale = ZonedDNSSetNames{}
 	}
 	equivEntries := dns.DNSNameSet{}
-	deleting := true // TODO check
+	deleting := false
 	domain := zone.Domain()
 	// fallback if no forwarded domains are reported
 	nested := utils.NewStringSet()
@@ -532,18 +522,14 @@ func (this *state) addEntriesForZone(
 			} else if provider == nil {
 				continue
 			} else if !provider.IncludesZone(zone.Id()) {
-				if provider.HasEquivalentZone(zone.Id()) && e.IsActive() && !forwarded(nested, dns.DNSName) {
+				if provider.HasEquivalentZone(zone.Id()) && !forwarded(nested, dns.DNSName) {
 					equivEntries.Add(dns.DNSSetName)
 				}
 				continue
 			}
 			if dns.ZoneID == zone.Id() && zone.Match(dns.DNSName) > 0 && !forwarded(nested, dns.DNSName) {
-				if e.IsActive() {
-					deleting = deleting || e.IsDeleting()
-					entries[e.ObjectName()] = e
-				} else {
-					logger.Infof("entry %q(%s) is inactive", e.ObjectName(), e.DNSName())
-				}
+				deleting = deleting || e.IsDeleting()
+				entries[e.ObjectName()] = e
 			}
 		} else {
 			if !e.IsDeleting() {
@@ -608,14 +594,6 @@ loop:
 	return found
 }
 
-func (this *state) triggerStatistic() {
-	if this.context.IsReady() {
-		_ = this.context.EnqueueCommand(CMD_STATISTIC)
-	} else {
-		this.setup.AddCommand(CMD_STATISTIC)
-	}
-}
-
 func (this *state) triggerHostedZone(zoneid dns.ZoneID) {
 	cmd := CMD_HOSTEDZONE_PREFIX + zoneid.ProviderType + ":" + zoneid.ID
 	if this.context.IsReady() {
diff --git a/pkg/dns/provider/state_entry.go b/pkg/dns/provider/state_entry.go
index 40e14b4d..2371ae40 100644
--- a/pkg/dns/provider/state_entry.go
+++ b/pkg/dns/provider/state_entry.go
@@ -16,6 +16,7 @@ import (
 	api "github.com/gardener/external-dns-management/pkg/apis/dns/v1alpha1"
 	"github.com/gardener/external-dns-management/pkg/dns"
 	perrs "github.com/gardener/external-dns-management/pkg/dns/provider/errors"
+	"github.com/gardener/external-dns-management/pkg/dns/provider/zonetxn"
 	dnsutils "github.com/gardener/external-dns-management/pkg/dns/utils"
 	"github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
 	"k8s.io/utils/ptr"
@@ -103,34 +104,37 @@ func (this *state) addEntryVersion(logger logger.LogContext, v *EntryVersion, st
 
 	var new *Entry
 	old := this.entries[v.ObjectName()]
+	var oldDNSSet *dns.DNSSet
 	if old == nil {
 		new = NewEntry(v, this)
 	} else {
+		oldDNSSet = this.dnsSetFromEntry(old)
 		new = old.Update(logger, v)
 	}
 
 	if v.IsDeleting() {
 		var err error
 		if old != nil {
-			this.cleanupEntry(logger, old)
+			this.cleanupEntry(logger, old, oldDNSSet)
 		}
 		if new.valid {
 			if !new.activezone.IsEmpty() && this.zones[new.activezone] != nil {
 				if this.HasFinalizer(new.Object()) {
 					logger.Infof("deleting delayed until entry deleted in provider")
 					this.outdated.AddEntry(new)
+					new.modified = true
 					return new, reconcile.Succeeded(logger)
 				}
 			} else {
 				if old != nil {
 					logger.Infof("dns zone '%s' of deleted entry gone", old.ZoneId())
 				}
-				if !new.IsActive() || v.object.Status().Zone == nil {
+				if v.object.Status().Zone == nil {
 					err = this.RemoveFinalizer(v.object)
 				}
 			}
 		} else {
-			if !new.IsActive() || v.object.Status().State != api.STATE_STALE {
+			if v.object.Status().State != api.STATE_STALE {
 				this.smartInfof(logger, "deleting yet unmanaged or errorneous entry")
 				err = this.RemoveFinalizer(v.object)
 			} else {
@@ -156,7 +160,7 @@ func (this *state) addEntryVersion(logger logger.LogContext, v *EntryVersion, st
 	if old != nil && old != new {
 		// DNS name changed -> clean up old dns name
 		logger.Infof("dns name changed to %q", new.ZonedDNSName())
-		this.cleanupEntry(logger, old)
+		this.cleanupEntry(logger, old, oldDNSSet)
 		if !old.activezone.IsEmpty() && old.activezone != new.ZoneId() {
 			if this.zones[old.activezone] != nil {
 				logger.Infof("dns zone changed -> trigger old zone '%s'", old.ZoneId())
@@ -207,6 +211,9 @@ func (this *state) addEntryVersion(logger logger.LogContext, v *EntryVersion, st
 		}
 
 		this.dnsnames[zonedDNSName] = new
+		if txn := this.getActiveZoneTransaction(new.activezone); txn != nil {
+			txn.AddEntryChange(new.ObjectKey(), new.object.GetGeneration(), oldDNSSet, this.dnsSetFromEntry(new))
+		}
 	}
 
 	return new, status
@@ -288,7 +295,6 @@ func (this *state) HandleUpdateEntry(logger logger.LogContext, op string, object
 		}
 	}
 
-	defer this.triggerStatistic()
 	defer this.references.NotifyHolder(this.context, object.ClusterKey())
 
 	logger = this.RefineLogger(logger, p.ptype)
@@ -338,14 +344,14 @@ func (this *state) EntryDeleted(logger logger.LogContext, key resources.ClusterO
 		} else {
 			this.smartInfof(logger, "removing foreign entry %q (%s)", key.ObjectName(), old.ZonedDNSName())
 		}
-		this.cleanupEntry(logger, old)
+		this.cleanupEntry(logger, old, this.dnsSetFromEntry(old))
 	} else {
 		logger.Debugf("removing unknown entry %q", key.ObjectName())
 	}
 	return reconcile.Succeeded(logger)
 }
 
-func (this *state) cleanupEntry(logger logger.LogContext, e *Entry) {
+func (this *state) cleanupEntry(logger logger.LogContext, e *Entry, oldDNSSet *dns.DNSSet) {
 	this.smartInfof(logger, "cleanup old entry (duplicate=%t)", e.duplicate)
 	this.entries.Delete(e)
 	this.DeleteLookupJob(e.ObjectName())
@@ -363,6 +369,9 @@ func (this *state) cleanupEntry(logger logger.LogContext, e *Entry) {
 				}
 			}
 		}
+		if txn := this.getActiveZoneTransaction(e.activezone); txn != nil {
+			txn.AddEntryChange(e.ObjectKey(), e.object.GetGeneration(), oldDNSSet, nil)
+		}
 		if found == nil {
 			logger.Infof("no duplicate found to reactivate")
 		} else {
@@ -374,6 +383,9 @@ func (this *state) cleanupEntry(logger logger.LogContext, e *Entry) {
 				msg = fmt.Sprintf("reactivate duplicate for %s: %s", found.ZonedDNSName(), found.ObjectName())
 			}
 			logger.Info(msg)
+			if txn := this.getActiveZoneTransaction(found.activezone); txn != nil {
+				txn.AddEntryChange(e.ObjectKey(), e.object.GetGeneration(), nil, this.dnsSetFromEntry(found))
+			}
 			found.Trigger(nil)
 		}
 		delete(this.dnsnames, e.ZonedDNSName())
@@ -397,3 +409,11 @@ func ignoredByAnnotation(object *dnsutils.DNSEntryObject) (bool, string) {
 	}
 	return false, ""
 }
+
+func (this *state) dnsSetFromEntry(e *Entry) *dns.DNSSet {
+	set := dns.NewDNSSet(e.dnsSetName, e.routingPolicy)
+	if !e.activezone.IsEmpty() {
+		zonetxn.ApplySpec(set, e.activezone.ProviderType, e.object.GetTargetSpec(e))
+	}
+	return set
+}
diff --git a/pkg/dns/provider/state_owner.go b/pkg/dns/provider/state_owner.go
index aec674c6..3ef712bd 100644
--- a/pkg/dns/provider/state_owner.go
+++ b/pkg/dns/provider/state_owner.go
@@ -11,10 +11,7 @@ import (
 	"github.com/gardener/controller-manager-library/pkg/logger"
 	"github.com/gardener/controller-manager-library/pkg/resources"
 	"github.com/gardener/controller-manager-library/pkg/utils"
-	"github.com/gardener/external-dns-management/pkg/apis/dns/v1alpha1"
-	"github.com/gardener/external-dns-management/pkg/dns/provider/statistic"
 	dnsutils "github.com/gardener/external-dns-management/pkg/dns/utils"
-	"github.com/gardener/external-dns-management/pkg/server/metrics"
 )
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -62,7 +59,6 @@ func (this *state) UpdateOwner(logger logger.LogContext, owner *dnsutils.DNSOwne
 	logger.Debugf("       active owner ids %s", active)
 	if len(changed) > 0 {
 		this.TriggerEntriesByOwner(logger, changed)
-		this.TriggerHostedZonesByChangedOwners(logger, changed)
 	}
 	if statusActive := owner.Status().Active; statusActive == nil || *statusActive != owner.IsActive() {
 		isActive := owner.IsActive()
@@ -83,78 +79,6 @@ func (this *state) OwnerDeleted(logger logger.LogContext, key resources.ClusterO
 	logger.Debugf("       active owner ids %s", active)
 	if len(changed) > 0 {
 		this.TriggerEntriesByOwner(logger, changed)
-		this.TriggerHostedZonesByChangedOwners(logger, changed)
 	}
 	return reconcile.Succeeded(logger)
 }
-
-func (this *state) UpdateOwnerCounts(log logger.LogContext) {
-	if !this.initialized {
-		return
-	}
-	log.Infof("update owner statistic")
-	statistic := statistic.NewEntryStatistic()
-	this.UpdateStatistic(statistic)
-	types := this.GetHandlerFactory().TypeCodes()
-	metrics.UpdateOwnerStatistic(statistic, types)
-	changes := this.ownerCache.UpdateCountsWith(statistic.Owners, types)
-	if len(changes) > 0 {
-		log.Infof("found %d changes for owner usages", len(changes))
-		this.ownerupd <- changes
-	}
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
-func startOwnerUpdater(pctx ProviderContext, ownerresc resources.Interface) chan OwnerCounts {
-	log := pctx.AddIndent("updater: ")
-
-	requests := make(chan OwnerCounts, 2)
-	go func() {
-		log.Infof("starting owner count updater")
-		for {
-			select {
-			case <-pctx.GetContext().Done():
-				log.Infof("stopping owner updater")
-				return
-			case changes := <-requests:
-				log.Infof("starting owner update for %d changes", len(changes))
-				for n, counts := range changes {
-					log.Infof("  updating owner counts %v for %s", counts, n)
-					_, _, err := ownerresc.ModifyStatusByName(resources.NewObjectName(string(n)), func(data resources.ObjectData) (bool, error) {
-						owner, ok := data.(*v1alpha1.DNSOwner)
-						if !ok {
-							return false, fmt.Errorf("invalid owner object type %T", data)
-						}
-						mod := false
-						if owner.Status.Entries.ByType == nil {
-							owner.Status.Entries.ByType = ProviderTypeCounts{}
-						}
-						for t, v := range counts {
-							if owner.Status.Entries.ByType[t] != v {
-								mod = true
-								owner.Status.Entries.ByType[t] = v
-							}
-							if v == 0 {
-								delete(owner.Status.Entries.ByType, t)
-							}
-						}
-						sum := 0
-						for _, v := range owner.Status.Entries.ByType {
-							sum += v
-						}
-						if owner.Status.Entries.Amount != sum {
-							owner.Status.Entries.Amount = sum
-							mod = true
-						}
-						return mod, nil
-					})
-					if err != nil {
-						log.Errorf("update failed: %s", err)
-					}
-				}
-			}
-		}
-	}()
-	return requests
-}
diff --git a/pkg/dns/provider/state_zone.go b/pkg/dns/provider/state_zone.go
index c0fb0779..88973b51 100644
--- a/pkg/dns/provider/state_zone.go
+++ b/pkg/dns/provider/state_zone.go
@@ -12,7 +12,7 @@ import (
 	"github.com/gardener/controller-manager-library/pkg/controllermanager/controller/reconcile"
 	"github.com/gardener/controller-manager-library/pkg/ctxutil"
 	"github.com/gardener/controller-manager-library/pkg/logger"
-	"github.com/gardener/controller-manager-library/pkg/utils"
+	"github.com/gardener/external-dns-management/pkg/dns/provider/zonetxn"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/errors"
 
@@ -25,17 +25,6 @@ import (
 // state handling for zone reconcilation
 ////////////////////////////////////////////////////////////////////////////////
 
-func (this *state) TriggerHostedZonesByChangedOwners(logger logger.LogContext, changed utils.StringSet) {
-	this.lock.Lock()
-	defer this.lock.Unlock()
-	for zoneid, zone := range this.zones {
-		if intersection := zone.IntersectOwners(changed); len(intersection) > 0 {
-			logger.Infof("trigger zone %s because of changed owners %s", zoneid, intersection)
-			this.triggerHostedZone(zoneid)
-		}
-	}
-}
-
 func (this *state) GetZoneReconcilation(logger logger.LogContext, zoneid dns.ZoneID) (time.Duration, bool, *zoneReconciliation) {
 	req := &zoneReconciliation{
 		fhandler: this.context,
@@ -149,7 +138,6 @@ func (this *state) StartZoneReconcilation(logger logger.LogContext, req *zoneRec
 		defer func() {
 			logger.Infof("unlocking %d entries", len(list))
 			list.Unlock()
-			this.triggerStatistic()
 		}()
 		return true, this.reconcileZone(logger, req)
 	}
@@ -161,8 +149,17 @@ func (this *state) reconcileZone(logger logger.LogContext, req *zoneReconciliati
 	req.zone.SetNext(time.Now().Add(this.config.Delay))
 	metrics.ReportZoneEntries(zoneid, len(req.entries), len(req.stale))
 	logger.Infof("reconcile ZONE %s (%s) for %d dns entries (%d stale)", req.zone.Id(), req.zone.Domain(), len(req.entries), len(req.stale))
-	logger.Debugf("    ownerids: %s", req.ownership.GetIds())
-	changes := NewChangeModel(logger, req.ownership, req, this.config)
+
+	current := this.startZoneTransaction(req.zone.Id())
+	var oldDNSSets dns.DNSSets
+	if current != nil {
+		for _, x := range current.AllChanges() {
+			logger.Infof("txn-change: %s", x)
+		}
+		oldDNSSets = current.OldDNSSets()
+	}
+	changes := NewChangeModel(logger, req.ownership, req, this.config, oldDNSSets)
+
 	err := changes.Setup()
 	if err != nil {
 		req.zone.Failed()
@@ -210,6 +207,15 @@ func (this *state) reconcileZone(logger logger.LogContext, req *zoneReconciliati
 	this.outdated.AddActiveZoneTo(zoneid, &outdatedEntries)
 	for _, e := range outdatedEntries {
 		if changes.IsFailed(e.DNSSetName()) {
+			if oldSet, ok := oldDNSSets[e.DNSSetName()]; ok {
+				if txn := this.getActiveZoneTransaction(zoneid); txn != nil {
+					txn.AddEntryChange(e.ObjectKey(), e.Object().GetGeneration(), oldSet, nil)
+				} else {
+					logger.Warnf("cleanup postpone failure: missing zone for %s", e.ObjectName())
+				}
+			} else {
+				logger.Warnf("cleanup postpone failure: old set not found for %s", e.ObjectName())
+			}
 			continue
 		}
 		logger.Infof("cleanup outdated entry %q", e.ObjectName())
@@ -244,3 +250,32 @@ func (this *state) CreateStateTTLGetter(defaultStateTTL time.Duration) StateTTLG
 		return defaultStateTTL
 	}
 }
+
+func (this *state) getActiveZoneTransaction(zoneID dns.ZoneID) *zonetxn.PendingTransaction {
+	if zoneID.IsEmpty() {
+		return nil
+	}
+
+	this.zoneTransactionsLock.Lock()
+	defer this.zoneTransactionsLock.Unlock()
+
+	current := this.zoneTransactions[zoneID]
+	if current == nil {
+		current = zonetxn.NewZoneTransaction(zoneID)
+		this.zoneTransactions[zoneID] = current
+	}
+	return current
+}
+
+func (this *state) startZoneTransaction(zoneID dns.ZoneID) *zonetxn.PendingTransaction {
+	if zoneID.IsEmpty() {
+		return nil
+	}
+
+	this.zoneTransactionsLock.Lock()
+	defer this.zoneTransactionsLock.Unlock()
+
+	current := this.zoneTransactions[zoneID]
+	delete(this.zoneTransactions, zoneID)
+	return current
+}
diff --git a/pkg/dns/provider/statistic/statistic.go b/pkg/dns/provider/statistic/statistic.go
index f7fe4b0e..3211de13 100644
--- a/pkg/dns/provider/statistic/statistic.go
+++ b/pkg/dns/provider/statistic/statistic.go
@@ -78,50 +78,10 @@ func (this ProviderTypeStatistic) Walk(state WalkingState, walker OwnerWalker, o
 
 ////////////////////////////////////////////////////////////////////////////////
 
-type OwnerStatistic map[string]ProviderTypeStatistic
-
-func (this OwnerStatistic) Inc(owner, ptype string, pname resources.ObjectName) {
-	this.Assure(owner).Inc(ptype, pname)
-}
-
-func (this OwnerStatistic) Count() int {
-	sum := 0
-	for _, e := range this {
-		sum += e.Count()
-	}
-	return sum
-}
-
-func (this OwnerStatistic) Get(owner string) ProviderTypeStatistic {
-	if pts := this[owner]; pts != nil {
-		return pts
-	}
-	return ProviderTypeStatistic{}
-}
-
-func (this OwnerStatistic) Assure(owner string) ProviderTypeStatistic {
-	cur := this[owner]
-	if cur == nil {
-		cur = ProviderTypeStatistic{}
-		this[owner] = cur
-	}
-	return cur
-}
-
-func (this OwnerStatistic) Walk(state WalkingState, walker OwnerWalker) WalkingState {
-	for o, os := range this {
-		state = os.Walk(state, walker, o)
-	}
-	return state
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
 type EntryStatistic struct {
 	Providers ProviderTypeStatistic
-	Owners    OwnerStatistic
 }
 
 func NewEntryStatistic() *EntryStatistic {
-	return &EntryStatistic{ProviderTypeStatistic{}, OwnerStatistic{}}
+	return &EntryStatistic{ProviderTypeStatistic{}}
 }
diff --git a/pkg/dns/provider/zone.go b/pkg/dns/provider/zone.go
index e9d9248d..fa4cf0d4 100644
--- a/pkg/dns/provider/zone.go
+++ b/pkg/dns/provider/zone.go
@@ -24,7 +24,6 @@ type dnsHostedZone struct {
 	zone        DNSHostedZone
 	next        time.Time
 	nextTrigger time.Duration
-	owners      utils.StringSet
 	policy      *dnsHostedZonePolicy
 }
 
@@ -32,7 +31,6 @@ func newDNSHostedZone(min time.Duration, zone DNSHostedZone) *dnsHostedZone {
 	return &dnsHostedZone{
 		zone:        zone,
 		RateLimiter: dnsutils.NewRateLimiter(min, 10*time.Minute),
-		owners:      utils.StringSet{},
 	}
 }
 
@@ -88,18 +86,6 @@ func (this *dnsHostedZone) Match(dnsname string) int {
 	return Match(this, dnsname)
 }
 
-func (this *dnsHostedZone) SetOwners(owners utils.StringSet) {
-	this.lock.Lock()
-	defer this.lock.Unlock()
-	this.owners = owners
-}
-
-func (this *dnsHostedZone) IntersectOwners(owners utils.StringSet) utils.StringSet {
-	this.lock.Lock()
-	defer this.lock.Unlock()
-	return this.owners.Intersect(owners)
-}
-
 func (this *dnsHostedZone) GetNext() time.Time {
 	this.lock.Lock()
 	defer this.lock.Unlock()
diff --git a/pkg/dns/provider/zonecache.go b/pkg/dns/provider/zonecache.go
index be44d0a8..ade12786 100644
--- a/pkg/dns/provider/zonecache.go
+++ b/pkg/dns/provider/zonecache.go
@@ -250,23 +250,7 @@ func (s *zoneStates) GetZoneState(zone DNSHostedZone, cache *defaultZoneCache) (
 	return state, true, nil
 }
 
-func (s *zoneStates) ReportZoneStateConflict(zoneID dns.ZoneID, err error) bool {
-	proxy := s.getProxy(zoneID)
-	proxy.lock.Lock()
-	defer proxy.lock.Unlock()
-
-	if !proxy.lastUpdateStart.IsZero() {
-		ownerConflict, ok := err.(*errors.AlreadyBusyForOwner)
-		if ok {
-			if ownerConflict.EntryCreatedAt.After(proxy.lastUpdateStart) {
-				// If a DNSEntry ownership is moved to another DNS controller manager (e.g. shoot recreation on another seed)
-				// the zone cache may have stale owner information. In this case the cache is invalidated
-				// if the entry is newer than the last cache refresh.
-				s.cleanZoneState(zoneID, proxy)
-				return true
-			}
-		}
-	}
+func (s *zoneStates) ReportZoneStateConflict(_ dns.ZoneID, _ error) bool {
 	return false
 }
 
diff --git a/pkg/dns/provider/zonetxn/change.go b/pkg/dns/provider/zonetxn/change.go
new file mode 100644
index 00000000..32f5e37f
--- /dev/null
+++ b/pkg/dns/provider/zonetxn/change.go
@@ -0,0 +1,110 @@
+// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package zonetxn
+
+import (
+	"bytes"
+	"fmt"
+	"sync"
+
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	"github.com/gardener/external-dns-management/pkg/dns"
+)
+
+type PendingTransaction struct {
+	lock sync.Mutex
+
+	zoneID             dns.ZoneID
+	pendingGenerations map[client.ObjectKey]int64
+
+	oldItems map[dns.DNSSetName][]*dns.DNSSet
+	newItems dns.DNSSets
+}
+
+func NewZoneTransaction(zoneID dns.ZoneID) *PendingTransaction {
+	return &PendingTransaction{
+		zoneID:             zoneID,
+		pendingGenerations: map[client.ObjectKey]int64{},
+		oldItems:           map[dns.DNSSetName][]*dns.DNSSet{},
+		newItems:           dns.DNSSets{},
+	}
+}
+
+func (t *PendingTransaction) ZoneID() dns.ZoneID {
+	return t.zoneID
+}
+
+func (t *PendingTransaction) AddEntryChange(key client.ObjectKey, generation int64, old, new *dns.DNSSet) {
+	if old.Match(new) {
+		return
+	}
+
+	t.lock.Lock()
+	defer t.lock.Unlock()
+
+	t.pendingGenerations[key] = generation
+
+	if old != nil {
+		t.oldItems[old.Name] = append(t.oldItems[old.Name], old)
+	}
+	if new != nil {
+		if other := t.newItems[new.Name]; other != nil {
+			t.oldItems[new.Name] = append(t.oldItems[new.Name], other)
+		}
+		t.newItems[new.Name] = new
+	}
+}
+
+func (t *PendingTransaction) AllChanges() map[dns.DNSSetName]Change {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+
+	changes := map[dns.DNSSetName]Change{}
+	for name, oldSets := range t.oldItems {
+		change := Change{
+			old: oldSets,
+		}
+		change.new = t.newItems[name]
+		changes[name] = change
+	}
+	for name, newSet := range t.newItems {
+		if _, found := changes[name]; !found {
+			changes[name] = Change{new: newSet}
+		}
+	}
+	return changes
+}
+
+func (t *PendingTransaction) OldDNSSets() dns.DNSSets {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+
+	sets := dns.DNSSets{}
+	for name, oldSetList := range t.oldItems {
+		for _, set := range oldSetList {
+			for _, recordset := range set.Sets {
+				sets.AddRecordSet(name, set.RoutingPolicy, recordset)
+			}
+		}
+	}
+	return sets
+}
+
+type Change struct {
+	old []*dns.DNSSet
+	new *dns.DNSSet
+}
+
+func (c Change) String() string {
+	var buf bytes.Buffer
+	for _, old := range c.old {
+		fmt.Fprintf(&buf, "old: %+v,", old)
+	}
+	if c.new != nil {
+		fmt.Fprintf(&buf, "new: %+v", c.new)
+	}
+	return buf.String()
+}
diff --git a/pkg/dns/provider/zonetxn/mapping.go b/pkg/dns/provider/zonetxn/mapping.go
new file mode 100644
index 00000000..3ab9386b
--- /dev/null
+++ b/pkg/dns/provider/zonetxn/mapping.go
@@ -0,0 +1,22 @@
+// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package zonetxn
+
+import (
+	"github.com/gardener/external-dns-management/pkg/controller/provider/aws/mapping"
+	"github.com/gardener/external-dns-management/pkg/dns"
+	"github.com/gardener/external-dns-management/pkg/dns/utils"
+)
+
+func ApplySpec(set *dns.DNSSet, providerType string, spec utils.TargetSpec) *dns.DNSSet {
+	targets := spec.Targets()
+	if providerType == "aws-route53" {
+		targets = mapping.MapTargets(spec.Targets())
+	}
+	for _, t := range targets {
+		set.Sets.AddRecord(t.GetRecordType(), t.GetHostName(), t.GetTTL())
+	}
+	return set
+}
diff --git a/pkg/dns/records.go b/pkg/dns/records.go
index 2bc3bcd4..d789ef46 100644
--- a/pkg/dns/records.go
+++ b/pkg/dns/records.go
@@ -11,7 +11,6 @@ import (
 )
 
 const (
-	RS_META       = "META"
 	RS_ALIAS_A    = "ALIAS"      // provider specific alias for CNAME record (AWS alias target A)
 	RS_ALIAS_AAAA = "ALIAS_AAAA" // provider specific alias for CNAME record (AWS alias target AAAA)
 )
@@ -130,7 +129,7 @@ func (rs *RecordSet) Match(set *RecordSet) bool {
 }
 
 func (rs *RecordSet) GetAttr(name string) string {
-	if rs.Type == RS_TXT || rs.Type == RS_META {
+	if rs.Type == RS_TXT {
 		prefix := newAttrKeyPrefix(name)
 		for _, r := range rs.Records {
 			if strings.HasPrefix(r.Value, prefix) {
diff --git a/pkg/dns/records_test.go b/pkg/dns/records_test.go
index 563a14ef..af8c9116 100644
--- a/pkg/dns/records_test.go
+++ b/pkg/dns/records_test.go
@@ -17,15 +17,15 @@ func TestMatch(t *testing.T) {
 		recordSetsAreEqual bool
 	}{
 		// Equal Sets
-		{RecordSet{Type: RS_META, TTL: 600, Records: []*Record{{"\"owner=test\""}}}, RecordSet{Type: RS_META, TTL: 600, Records: []*Record{{"\"owner=test\""}}}, true},
-		// RecordSet type not equal TTL & records equal = equal
-		{RecordSet{Type: RS_META, TTL: 600, Records: []*Record{{"\"owner=test\""}}}, RecordSet{Type: RS_TXT, TTL: 600, Records: []*Record{{"\"owner=test\""}}}, true},
+		{RecordSet{Type: RS_TXT, TTL: 600, Records: []*Record{{"\"foo\""}}}, RecordSet{Type: RS_TXT, TTL: 600, Records: []*Record{{"\"foo\""}}}, true},
 		// One record value different = not equal
-		{RecordSet{Type: RS_META, TTL: 600, Records: []*Record{{"\"owner=test\""}}}, RecordSet{Type: RS_META, TTL: 600, Records: []*Record{{"xx.xx.xx.xx"}}}, false},
+		{RecordSet{Type: RS_TXT, TTL: 600, Records: []*Record{{"\"foo\""}}}, RecordSet{Type: RS_TXT, TTL: 600, Records: []*Record{{"xx.xx.xx.xx"}}}, false},
 		// Equal except for TTL = not equal
-		{RecordSet{Type: RS_META, TTL: 600, Records: []*Record{{"\"owner=test\""}}}, RecordSet{Type: RS_TXT, TTL: 800, Records: []*Record{{"\"owner=test\""}}}, false},
+		{RecordSet{Type: RS_TXT, TTL: 600, Records: []*Record{{"\"foo\""}}}, RecordSet{Type: RS_TXT, TTL: 800, Records: []*Record{{"\"foo\""}}}, false},
 		// different amount of records = not equal
-		{RecordSet{Type: RS_META, TTL: 600, Records: []*Record{{"\"owner=test\""}}}, RecordSet{Type: RS_TXT, TTL: 600, Records: []*Record{{"\"owner=test\""}, {"\"owner=test\""}}}, false},
+		{RecordSet{Type: RS_TXT, TTL: 600, Records: []*Record{{"\"foo\""}}}, RecordSet{Type: RS_TXT, TTL: 600, Records: []*Record{{"\"foo\""}, {"\"foo\""}}}, false},
+		// different type = not equal
+		{RecordSet{Type: RS_A, TTL: 600, Records: []*Record{{"1.2.3.4"}}}, RecordSet{Type: RS_TXT, TTL: 600, Records: []*Record{{"\"foo\""}}}, false},
 	}
 
 	for _, entry := range table {
diff --git a/pkg/dns/utils/target.go b/pkg/dns/utils/target.go
index ec93b0e5..dbb0e8fd 100644
--- a/pkg/dns/utils/target.go
+++ b/pkg/dns/utils/target.go
@@ -84,46 +84,27 @@ func (t *target) String() string {
 ////////////////////////////////////////////////////////////////////////////////
 
 type TargetSpec interface {
-	Kind() string
-	OwnerId() string
 	Targets() []Target
 	RoutingPolicy() *dns.RoutingPolicy
-	Responsible(set *dns.DNSSet, ownership dns.Ownership) bool
 }
 
 type targetSpec struct {
-	kind          string
-	ownerId       string
 	targets       []Target
 	routingPolicy *dns.RoutingPolicy
 }
 
 func BaseTargetSpec(entry *DNSEntryObject, p TargetProvider) TargetSpec {
 	spec := &targetSpec{
-		kind:          entry.GroupKind().Kind,
-		ownerId:       p.OwnerId(),
 		targets:       p.Targets(),
 		routingPolicy: p.RoutingPolicy(),
 	}
 	return spec
 }
 
-func (this *targetSpec) Kind() string {
-	return this.kind
-}
-
-func (this *targetSpec) OwnerId() string {
-	return this.ownerId
-}
-
 func (this *targetSpec) Targets() []Target {
 	return this.targets
 }
 
-func (this *targetSpec) Responsible(set *dns.DNSSet, ownership dns.Ownership) bool {
-	return !set.IsForeign(ownership)
-}
-
 func (this *targetSpec) RoutingPolicy() *dns.RoutingPolicy {
 	return this.routingPolicy
 }
diff --git a/pkg/dns/utils/utils_dns.go b/pkg/dns/utils/utils_dns.go
index 856d410c..36171291 100644
--- a/pkg/dns/utils/utils_dns.go
+++ b/pkg/dns/utils/utils_dns.go
@@ -11,7 +11,6 @@ import (
 type TargetProvider interface {
 	Targets() Targets
 	TTL() int64
-	OwnerId() string
 	RoutingPolicy() *dns.RoutingPolicy
 }
 
diff --git a/pkg/dns/validation.go b/pkg/dns/validation.go
index 4db9d9c5..65ec0185 100644
--- a/pkg/dns/validation.go
+++ b/pkg/dns/validation.go
@@ -32,16 +32,6 @@ func ValidateDomainName(name string) error {
 		return fmt.Errorf("%q is no valid dns name (%v)", name, errs)
 	}
 
-	metaCheck := CalcMetaRecordDomainNameForValidation(check)
-	if strings.HasPrefix(metaCheck, "*.") {
-		errs = validation.IsWildcardDNS1123Subdomain(metaCheck)
-	} else {
-		errs = validation.IsDNS1123Subdomain(metaCheck)
-	}
-	if len(errs) > 0 {
-		return fmt.Errorf("metadata record %q of %q is no valid dns name (%v)", metaCheck, name, errs)
-	}
-
 	labels := strings.Split(strings.TrimPrefix(check, "*."), ".")
 	for i, label := range labels {
 		if i == 0 && label == "@" {
@@ -52,10 +42,6 @@ func ValidateDomainName(name string) error {
 			return fmt.Errorf("%d. label %q of %q is not valid (%v)", i+1, label, name, errs)
 		}
 	}
-	metaLabels := strings.SplitN(strings.TrimPrefix(metaCheck, "*."), ".", 2)
-	if errs = validation.IsDNS1123Label(metaLabels[0]); len(errs) > 0 {
-		return fmt.Errorf("1. label %q of metadata record of %q is not valid (%v)", metaLabels[0], name, errs)
-	}
 
 	return nil
 }
diff --git a/pkg/dns/validation_test.go b/pkg/dns/validation_test.go
index cd84ffa1..4922912b 100644
--- a/pkg/dns/validation_test.go
+++ b/pkg/dns/validation_test.go
@@ -32,10 +32,7 @@ func TestValidation(t *testing.T) {
 		{"a123456789012345678901234567890123456789012345678901234567890abc.b", false},   // label too long
 		{"a.a123456789012345678901234567890123456789012345678901234567890abc.b", false}, // label too long
 		{"a12345678901234567890123456789012345678901234567890abcd.b", true},
-		{"a12345678901234567890123456789012345678901234567890abcde.b", false}, // meta data label too long
-		{"abc.a123456789." + name239, false},                                  // name too long
-		{"abcde." + name239, true},                                            // comment-abcde... has 253 chars
-		{"abcdef." + name239, false},                                          // meta data name too long
+		{"abc.a123456789." + name239, false}, // name too long
 	}
 	for _, entry := range table {
 		err := ValidateDomainName(entry.input)
diff --git a/pkg/server/metrics/metrics.go b/pkg/server/metrics/metrics.go
index 54bfb75d..f9389a28 100644
--- a/pkg/server/metrics/metrics.go
+++ b/pkg/server/metrics/metrics.go
@@ -17,7 +17,6 @@ import (
 	"github.com/gardener/controller-manager-library/pkg/server"
 	"github.com/gardener/controller-manager-library/pkg/utils"
 	"github.com/gardener/external-dns-management/pkg/dns"
-	"github.com/gardener/external-dns-management/pkg/dns/provider/statistic"
 )
 
 func init() {
@@ -293,57 +292,6 @@ func DeleteZone(zoneid dns.ZoneID) {
 	Entries.DeleteLabelValues(zoneid.ProviderType, zoneid.ID)
 }
 
-var (
-	currentStatistic = statistic.NewEntryStatistic()
-	lock             sync.Mutex
-)
-
-func deleteOwnerStatistic(state statistic.WalkingState, owner, ptype string, pname resources.ObjectName, _ int) statistic.WalkingState {
-	types := state.(utils.StringSet)
-	if types.Contains(ptype) {
-		Owners.DeleteLabelValues(owner, ptype, pname.String())
-	}
-	return state
-}
-
-func UpdateOwnerStatistic(statistic *statistic.EntryStatistic, types utils.StringSet) {
-	lock.Lock()
-	defer lock.Unlock()
-
-	for o := range currentStatistic.Owners {
-		statistic.Owners.Assure(o)
-	}
-	for o, pts := range statistic.Owners {
-		old_pts := currentStatistic.Owners.Assure(o)
-		for pt := range types {
-			ps := pts.Get(pt)
-			old_ps := old_pts.Assure(pt)
-			for p, c := range ps {
-				Owners.WithLabelValues(o, pt, p.String()).Set(float64(c))
-				old_ps[p] = c
-			}
-			for p := range old_ps {
-				if _, ok := ps[p]; !ok {
-					Owners.DeleteLabelValues(o, pt, p.String())
-					delete(old_ps, p)
-				}
-			}
-			if len(old_ps) == 0 {
-				delete(old_pts, pt)
-			}
-		}
-		for pt, ps := range old_pts {
-			if pts[pt] == nil && types.Contains(pt) {
-				ps.Walk(types, deleteOwnerStatistic, o, pt)
-				delete(old_pts, pt)
-			}
-		}
-		if len(old_pts) == 0 {
-			delete(currentStatistic.Owners, o)
-		}
-	}
-}
-
 func ReportLookupProcessorIncrSkipped() {
 	LookupProcessorSkips.Inc()
 }
diff --git a/pkg/server/remote/conversion/conversion_test.go b/pkg/server/remote/conversion/conversion_test.go
index 95ae461a..858dda52 100644
--- a/pkg/server/remote/conversion/conversion_test.go
+++ b/pkg/server/remote/conversion/conversion_test.go
@@ -76,15 +76,13 @@ func doTestMarshalChangeRequest(t *testing.T, withPolicy bool) {
 	}
 	set := dns.NewDNSSet(dns.DNSSetName{DNSName: "b.a", SetIdentifier: setIdentifier}, routingPolicy)
 	set.UpdateGroup = "group1"
-	set.SetMetaAttr(dns.ATTR_OWNER, "owner1")
-	set.SetMetaAttr(dns.ATTR_PREFIX, "comment-")
 	set.SetRecordSet(dns.RS_A, 100, "1.1.1.1", "1.1.1.2")
 	table := []struct {
 		name    string
 		request *provider.ChangeRequest
 	}{
 		{"create", provider.NewChangeRequest(provider.R_CREATE, dns.RS_A, nil, set, nil)},
-		{"update", provider.NewChangeRequest(provider.R_UPDATE, dns.RS_META, nil, set, nil)},
+		{"update", provider.NewChangeRequest(provider.R_UPDATE, dns.RS_A, nil, set, nil)},
 		{"delete", provider.NewChangeRequest(provider.R_DELETE, dns.RS_A, set, nil, nil)},
 	}
 
diff --git a/test/integration/compound/compound_test.go b/test/integration/compound/compound_test.go
index c3ba4a78..131df5b0 100644
--- a/test/integration/compound/compound_test.go
+++ b/test/integration/compound/compound_test.go
@@ -60,8 +60,7 @@ var _ = Describe("Compound controller tests", func() {
 					ExpectWithOffset(1, zoneDump.DNSSets).To(HaveLen(1), "unexpected number of DNS sets in first.example.com")
 					set := zoneDump.DNSSets[dns.DNSSetName{DNSName: entry.Spec.DNSName}]
 					ExpectWithOffset(1, set.Sets).To(HaveKey("A"))
-					ExpectWithOffset(1, set.Sets).To(HaveKey("META"))
-					ExpectWithOffset(1, set.Sets).To(HaveLen(2))
+					ExpectWithOffset(1, set.Sets).To(HaveLen(1))
 					setA := set.Sets["A"]
 					ExpectWithOffset(1, setA.Records).To(ConsistOf(
 						PointTo(MatchFields(IgnoreExtras, Fields{
@@ -270,6 +269,7 @@ var _ = Describe("Compound controller tests", func() {
 		By("check mock database")
 		checkSingleEntryInMockDatabase(e4)
 
+		By("await deletion of entry " + e4.Name)
 		Expect(testClient.Delete(ctx, e4)).To(Succeed())
 		Eventually(func(g Gomega) {
 			checkDeleted(g, ctx, e4)
@@ -295,7 +295,7 @@ var _ = Describe("Compound controller tests", func() {
 
 		Eventually(func(g Gomega) {
 			g.Expect(testClient.Get(ctx, client.ObjectKeyFromObject(e1), e1)).To(Succeed())
-			g.Expect(e1.Status.State).To(Or(Equal("Error"), Equal("Stale")))
+			g.Expect(e1.Status.State).To(Equal("Error"))
 			g.Expect(e1.Status.ObservedGeneration).To(Equal(e1.Generation))
 		}).Should(Succeed())
 
@@ -346,7 +346,7 @@ var _ = Describe("Compound controller tests", func() {
 
 		Eventually(func(g Gomega) {
 			g.Expect(testClient.Get(ctx, client.ObjectKeyFromObject(e1), e1)).To(Succeed())
-			g.Expect(e1.Status.State).To(Or(Equal("Error"), Equal("Stale")))
+			g.Expect(e1.Status.State).To(Equal("Error"))
 			g.Expect(e1.Status.ObservedGeneration).To(Equal(e1.Generation))
 		}).Should(Succeed())
 
@@ -380,29 +380,17 @@ var _ = Describe("Compound controller tests", func() {
 			Type:     "A",
 			Deletion: deleteSet,
 		})
-		deleteSet2 := dns.NewDNSSet(dns.DNSSetName{DNSName: "e2.first.example.com"}, nil)
-		deleteSet2.Sets.AddRecord("META", "\"owner=dnscontroller\"", 600)
-		deleteSet2.Sets.AddRecord("META", "\"prefix=comment-\"", 600)
-		failID2 := mock.TestMock[testRunID].AddApplyFailSimulation(firstZoneID, &provider.ChangeRequest{
-			Action:   provider.R_DELETE,
-			Type:     "META",
-			Deletion: deleteSet2,
-		})
 		Expect(testClient.Delete(ctx, e2)).To(Succeed())
 
 		Eventually(func() int {
 			return mock.TestMock[testRunID].GetApplyFailSimulationCount(failID)
 		}).ShouldNot(BeZero())
-		Eventually(func() int {
-			return mock.TestMock[testRunID].GetApplyFailSimulationCount(failID2)
-		}).ShouldNot(BeZero())
 
 		Expect(testClient.Get(ctx, client.ObjectKeyFromObject(e2), e2)).To(Succeed())
 		Expect(e2.DeletionTimestamp).NotTo(BeNil())
 
 		// remove apply fail simulation
 		mock.TestMock[testRunID].RemoveApplyFailSimulation(failID)
-		mock.TestMock[testRunID].RemoveApplyFailSimulation(failID2)
 		By("await deletion of entry " + e2.Name)
 		Eventually(func(g Gomega) {
 			checkDeleted(g, ctx, e2)

From f3ee821ead1326bd36d53a05fa2160ae57abd6f7 Mon Sep 17 00:00:00 2001
From: Martin Weindel <martin.weindel@sap.com>
Date: Mon, 9 Dec 2024 17:11:55 +0100
Subject: [PATCH 02/10] command line options and updated README

---
 README.md                                     | 85 ++++++++-----------
 .../templates/deployment.yaml                 | 12 +--
 charts/external-dns-management/values.yaml    |  4 +-
 examples/60-owner.yaml                        |  2 +
 4 files changed, 45 insertions(+), 58 deletions(-)

diff --git a/README.md b/README.md
index 1107fa2d..b8cfd4ae 100644
--- a/README.md
+++ b/README.md
@@ -33,26 +33,28 @@ For extending or adapting this project with your own source or provisioning cont
 
 ## Index
 
-* [Quick start](#quick-start)
-  * [Automatic creation of DNS entries for services and ingresses](#automatic-creation-of-dns-entries-for-services-and-ingresses)
-    * [`A` DNS records with alias targets for provider type AWS-Route53 and AWS load balancers](#a-dns-records-with-alias-targets-for-provider-type-aws-route53-and-aws-load-balancers) 
-  * [Automatic creation of DNS entries for gateways](#automatic-creation-of-dns-entries-for-gateways)
-    * [Istio gateways](#istio-gateways)
-    * [Gateway API gateways](#gateway-api-gateways)
-* [The Model](#the-model)
-  * [Owner Identifiers](#owner-identifiers)
-  * [DNS Classes](#dns-classes)
-  * [DNSAnnotation objects](#dnsannotation-objects)
-* [Using the DNS controller manager](#using-the-dns-controller-manager)
-* [Extensions](#extensions)
-  * [How to implement Source Controllers](#how-to-implement-source-controllers)
-  * [How to implement Provisioning Controllers](#how-to-implement-provisioning-controllers)
-    * [Embedding a Factory into a Controller](#embedding-a-factory-into-a-controller)
-    * [Embedding a Factory into a Compound Factory](#embedding-a-factory-into-a-compound-factory)
-  * [Setting Up a Controller Manager](#setting-up-a-controller-manager)
-  * [Using the standard Compound Provisioning Controller](#using-the-standard-compound-provisioning-controller)
-  * [Multiple Cluster Support](#multiple-cluster-support)
-* [Why not use the community `external-dns` solution?](#why-not-use-the-community-external-dns-solution)
+- [External DNS Management](#external-dns-management)
+  - [Index](#index)
+  - [Quick start](#quick-start)
+    - [Automatic creation of DNS entries for services and ingresses](#automatic-creation-of-dns-entries-for-services-and-ingresses)
+      - [`A` DNS records with alias targets for provider type AWS-Route53 and AWS load balancers](#a-dns-records-with-alias-targets-for-provider-type-aws-route53-and-aws-load-balancers)
+    - [Automatic creation of DNS entries for gateways](#automatic-creation-of-dns-entries-for-gateways)
+      - [Istio gateways](#istio-gateways)
+      - [Gateway API gateways](#gateway-api-gateways)
+  - [The Model](#the-model)
+    - [Owner Identifiers](#owner-identifiers)
+    - [DNS Classes](#dns-classes)
+    - [DNSAnnotation objects](#dnsannotation-objects)
+  - [Using the DNS controller manager](#using-the-dns-controller-manager)
+  - [Extensions](#extensions)
+    - [How to implement Source Controllers](#how-to-implement-source-controllers)
+    - [How to implement Provisioning Controllers](#how-to-implement-provisioning-controllers)
+      - [Embedding a Factory into a Controller](#embedding-a-factory-into-a-controller)
+      - [Embedding a Factory into a Compound Factory](#embedding-a-factory-into-a-compound-factory)
+    - [Setting Up a Controller Manager](#setting-up-a-controller-manager)
+    - [Using the standard Compound Provisioning Controller](#using-the-standard-compound-provisioning-controller)
+    - [Multiple Cluster Support](#multiple-cluster-support)
+  - [Why not use the community `external-dns` solution?](#why-not-use-the-community-external-dns-solution)
 
 ## Quick start
 
@@ -430,31 +432,15 @@ and/or DNS zone identifiers to override the scanning results of the account.
 
 ### Owner Identifiers
 
-Every DNS Provisioning Controller is responsible for a set of _Owner Identifiers_.
-DNS records in an external DNS environment are attached to such an identifier.
-This is used to identify the records in the DNS environment managed by a dedicated
-controller (manager). Every controller manager hosting DNS Provisioning Controllers
-offers an option to specify a default identifier. Additionally, there might
-be dedicated `DNSOwner` objects that enable or disable additional owner ids.
-
-Every `DNSEntry` object may specify a dedicated owner that is used to tag
-the records in the DNS environment. A DNS provisioning controller only acts
-on DNS entries it is responsible for. Other resources in the external DNS
-environment are not touched at all.
-
-This way it is possible to
-- identify records in the external DNS management environment that are managed
-  by the actual controller instance
-- distinguish different DNS source environments sharing the same hosted zones
-  in the external management environment
-- cleanup unused entries, even if the whole resource set is already
-  gone
-- move the responsibility for dedicated sets of DNS entries among different
-  Kubernetes clusters or DNS source environments running different
-  DNS Provisioning Controller without losing the entries during the
-  migration process.
-
-**If multiple DNS controller instances have access to the same DNS zones, it is very important, that every instance uses a unique owner identifier! Otherwise, the cleanup of stale DNS record will delete entries created by another instance if they use the same identifier.**
+Starting with release `v0.23`, owner identifier are no longer supported.
+Formerly, every DNS Provisioning Controller was responsible for a set of _Owner Identifiers_.
+For every DNS record, there was an additional `TXT` DNS record ("metadata record") referencing the owner identifier.
+It was decided to remove this feature, as it doubles the number of DNS records without adding
+enough value.
+
+In the release `v0.23`, it is still important to specify the `--identifier` option for the compound DNS
+Provisioning Controller and also to keep the `DNSOwner` resources as they are used to clean up the "metadata records".
+In the future, the `DNSOwner` resources will be removed completely.
 
 ### DNS Classes
 
@@ -560,7 +546,8 @@ unique controller identity using the `--identifier` option.
 This identifier is stored in the DNS system to identify the DNS entries
 managed by a dedicated controller. There should never be two
 DNS controllers with the same identifier running at the same time for the
-same DNS domains/accounts.
+same DNS domains/accounts. In release `v0.23`, the `--identifier` option is only used to 
+cleanup the "metadata records" created by the DNS Provisioning Controller.
 
 Here is the complete list of options provided:
 
@@ -664,6 +651,7 @@ Flags:
       --compound.infoblox-dns.ratelimiter.enabled                     enables rate limiter for DNS provider requests of controller compound
       --compound.infoblox-dns.ratelimiter.qps int                     maximum requests/queries per second of controller compound
       --compound.lock-status-check-period duration                    interval for dns lock status checks of controller compound
+      --compound.max-metadata-record-deletions-per-reconciliation int   maximum number of metadata owner records that can be deleted per zone reconciliation of controller compound
       --compound.netlify-dns.advanced.batch-size int                  batch size for change requests (currently only used for aws-route53) of controller compound
       --compound.netlify-dns.advanced.max-retries int                 maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53) of controller compound
       --compound.netlify-dns.blocked-zone zone-id                     Blocks a zone given in the format zone-id from a provider as if the zone is not existing. of controller compound
@@ -704,7 +692,6 @@ Flags:
       --compound.rfc2136.ratelimiter.qps int                          maximum requests/queries per second of controller compound
       --compound.secrets.pool.size int                                Worker pool size for pool secrets of controller compound
       --compound.setup int                                            number of processors for controller setup of controller compound
-      --compound.statistic.pool.size int                              Worker pool size for pool statistic of controller compound
       --compound.ttl int                                              Default time-to-live for DNS entries. Defines how long the record is kept in cache by DNS servers or resolvers. of controller compound
       --compound.zonepolicies.pool.size int                           Worker pool size for pool zonepolicies of controller compound
       --config string                                                 config file
@@ -836,6 +823,7 @@ Flags:
       --lock-status-check-period duration                             interval for dns lock status checks
   -D, --log-level string                                              logrus log level
       --maintainer string                                             maintainer key for crds (default "dns-controller-manager")
+      --max-metadata-record-deletions-per-reconciliation int          maximum number of metadata owner records that can be deleted per zone reconciliation      
       --name string                                                   name used for controller manager (default "dns-controller-manager")
       --namespace string                                              namespace for lease (default "kube-system")
   -n, --namespace-local-access-only                                   enable access restriction for namespace local access only (deprecated)
@@ -908,7 +896,6 @@ Flags:
       --service-dns.target-set-ignore-owners                          mark generated DNS entries to omit owner based access control of controller service-dns
       --service-dns.targets.pool.size int                             Worker pool size for pool targets of controller service-dns
       --setup int                                                     number of processors for controller setup
-      --statistic.pool.size int                                       Worker pool size for pool statistic
       --target string                                                 target cluster for dns requests
       --target-creator-label-name string                              label name to store the creator for replicated DNS providers, label name to store the creator for generated DNS entries
       --target-creator-label-value string                             label value for creator label
@@ -1202,8 +1189,6 @@ The Gardener DNS controller uses a custom resource DNSProvider to dynamically ma
 
 A DNS provider can also restrict its actions on subset of the DNS domains (includes and excludes) for which the credentials are capable to edit.
 
-Each provider can define a separate “owner” identifier, to differentiate DNS entries in the same DNS zone from different providers.
-
 3. Multi cluster support
 
 The Gardener DNS controller distinguish three different logical Kubernetes clusters: Source cluster, target cluster and runtime cluster. The source cluster is monitored by the DNS source controllers for annotations on ingress and service resources. These controllers then create DNS entries in the target cluster. DNS entries in the target cluster are then reconciled/synchronized with the corresponding DNS backend service by the provider controller. The runtime cluster is the cluster the DNS controller runs on. For example, this enables needed flexibility in the Gardener deployment. The DNS controller runs on the seed cluster. This is also the target cluster. DNS providers and entries resources are created in the corresponding namespace of the shoot control plane, while the source cluster is the shoot cluster itself.
diff --git a/charts/external-dns-management/templates/deployment.yaml b/charts/external-dns-management/templates/deployment.yaml
index d38c6fc6..9dc62e52 100644
--- a/charts/external-dns-management/templates/deployment.yaml
+++ b/charts/external-dns-management/templates/deployment.yaml
@@ -324,6 +324,9 @@ spec:
         {{- if .Values.configuration.compoundLockStatusCheckPeriod }}
         - --compound.lock-status-check-period={{ .Values.configuration.compoundLockStatusCheckPeriod }}
         {{- end }}
+        {{- if .Values.configuration.compoundMaxMetadataRecordDeletionsPerReconciliation }}
+        - --compound.max-metadata-record-deletions-per-reconciliation={{ .Values.configuration.compoundMaxMetadataRecordDeletionsPerReconciliation }}
+        {{- end }}
         {{- if .Values.configuration.compoundNetlifyDnsAdvancedBatchSize }}
         - --compound.netlify-dns.advanced.batch-size={{ .Values.configuration.compoundNetlifyDnsAdvancedBatchSize }}
         {{- end }}
@@ -420,9 +423,6 @@ spec:
         {{- if .Values.configuration.compoundSetup }}
         - --compound.setup={{ .Values.configuration.compoundSetup }}
         {{- end }}
-        {{- if .Values.configuration.compoundStatisticPoolSize }}
-        - --compound.statistic.pool.size={{ .Values.configuration.compoundStatisticPoolSize }}
-        {{- end }}
         {{- if .Values.configuration.compoundTtl }}
         - --compound.ttl={{ .Values.configuration.compoundTtl }}
         {{- end }}
@@ -801,6 +801,9 @@ spec:
         {{- if .Values.configuration.maintainer }}
         - --maintainer={{ .Values.configuration.maintainer }}
         {{- end }}
+        {{- if .Values.configuration.maxMetadataRecordDeletionsPerReconciliation }}
+        - --max-metadata-record-deletions-per-reconciliation={{ .Values.configuration.maxMetadataRecordDeletionsPerReconciliation }}
+        {{- end }}
         {{- if .Values.configuration.namespace }}
         - --namespace={{ .Values.configuration.namespace }}
         {{- end }}
@@ -975,9 +978,6 @@ spec:
         {{- if .Values.configuration.setup }}
         - --setup={{ .Values.configuration.setup }}
         {{- end }}
-        {{- if .Values.configuration.statisticPoolSize }}
-        - --statistic.pool.size={{ .Values.configuration.statisticPoolSize }}
-        {{- end }}
         {{- if .Values.configuration.target }}
         - --target={{ .Values.configuration.target }}
         {{- end }}
diff --git a/charts/external-dns-management/values.yaml b/charts/external-dns-management/values.yaml
index c5eb6f6c..99cc2da3 100644
--- a/charts/external-dns-management/values.yaml
+++ b/charts/external-dns-management/values.yaml
@@ -132,6 +132,7 @@ configuration:
   # compoundInfobloxDnsRatelimiterEnabled:
   # compoundInfobloxDnsRatelimiterQps:
   # compoundLockStatusCheckPeriod:
+  # compoundMaxMetadataRecordDeletionsPerReconciliation:
   # compoundNetlifyDnsAdvancedBatchSize:
   # compoundNetlifyDnsAdvancedMaxRetries:
   # compoundNetlifyDnsRatelimiterBurst:
@@ -164,7 +165,6 @@ configuration:
   # compoundRfc2136RatelimiterQps:
   # compoundSecretsPoolSize: 2
   # compoundSetup: 10
-  # compoundStatisticPoolSize:
   # compoundTtl: 120
   # compoundZonepoliciesPoolSize:
   # config:
@@ -291,6 +291,7 @@ configuration:
   # lockStatusCheckPeriod:
   # logLevel: info
   # maintainer:
+  # maxMetadataRecordDeletionsPerReconciliation:
   # namespace: default
   # namespaceLocalAccessOnly: false
   # netlifyDnsAdvancedBatchSize:
@@ -350,7 +351,6 @@ configuration:
   # serviceDNSTargetsPoolSize: 2
   # servicesPoolSize:
   # setup: 10
-  # statisticPoolSize:
   # target: ""
   # targetCreatorLabelName: ""
   # targetCreatorLabelValue: ""
diff --git a/examples/60-owner.yaml b/examples/60-owner.yaml
index 2f74fade..d4d82c0f 100644
--- a/examples/60-owner.yaml
+++ b/examples/60-owner.yaml
@@ -1,3 +1,5 @@
+# Please note that starting with release `v0.23` DNSOwner resources are only used to clean up metadata DNS records.
+# The owner identifiers are not used for any other purpose anymore.
 apiVersion: dns.gardener.cloud/v1alpha1
 kind: DNSOwner
 metadata:

From b6a9b2f8b0e99fb627bd201c8dcda3c1c8602eac Mon Sep 17 00:00:00 2001
From: Martin Weindel <martin.weindel@sap.com>
Date: Mon, 9 Dec 2024 17:45:12 +0100
Subject: [PATCH 03/10] drop obsolete tests

---
 README.md                               | 14 +++---
 test/integration/entryLivecycle_test.go | 61 +------------------------
 test/integration/testenv.go             | 11 -----
 3 files changed, 8 insertions(+), 78 deletions(-)

diff --git a/README.md b/README.md
index b8cfd4ae..5a97913e 100644
--- a/README.md
+++ b/README.md
@@ -541,13 +541,13 @@ The following provider types can be selected (comma separated):
 - `remote`: Remote DNS provider (a dns-controller-manager with enabled remote access service)
 - `powerdns`: PowerDNS provider
 
-If the compound DNS Provisioning Controller is enabled it is important to specify a
-unique controller identity using the `--identifier` option.
-This identifier is stored in the DNS system to identify the DNS entries
-managed by a dedicated controller. There should never be two
-DNS controllers with the same identifier running at the same time for the
-same DNS domains/accounts. In release `v0.23`, the `--identifier` option is only used to 
-cleanup the "metadata records" created by the DNS Provisioning Controller.
+If the compound DNS Provisioning Controller is enabled, a unique controller identity was specified using the
+`--identifier` option in former release.
+This identifier was used to tag the DNS entries managed by a dedicated controller by creating additional
+"metadata `TXT` records" in the DNS system.
+Starting with release `v0.23`, this feature has been dropped as it doubles the number of DNS records.
+It is still important and required to specify the `--identifier` option to enable the cleanup of "metadata records" 
+created by former releases of the DNS Provisioning Controller.
 
 Here is the complete list of options provided:
 
diff --git a/test/integration/entryLivecycle_test.go b/test/integration/entryLivecycle_test.go
index ef8a4a92..e1711fa1 100644
--- a/test/integration/entryLivecycle_test.go
+++ b/test/integration/entryLivecycle_test.go
@@ -104,46 +104,6 @@ var _ = Describe("EntryLivecycle", func() {
 		Ω(err).ShouldNot(HaveOccurred())
 	})
 
-	It("is handled only by owner", func() {
-		pr, domain, _, err := testEnv.CreateSecretAndProvider("inmemory.mock", 0)
-		Ω(err).ShouldNot(HaveOccurred())
-
-		defer testEnv.DeleteProviderAndSecret(pr)
-
-		e, err := testEnv.CreateEntry(0, domain)
-		Ω(err).ShouldNot(HaveOccurred())
-		defer testEnv.DeleteEntryAndWait(e)
-
-		checkProvider(pr)
-
-		checkEntry(e, pr)
-
-		ownerID := "my/owner1"
-		e, err = testEnv.UpdateEntryOwner(e, &ownerID)
-		Ω(err).ShouldNot(HaveOccurred())
-
-		err = testEnv.AwaitEntryStale(e.GetName())
-		Ω(err).ShouldNot(HaveOccurred())
-
-		owner1, err := testEnv.CreateOwner("owner1", ownerID)
-		Ω(err).ShouldNot(HaveOccurred())
-
-		defer func() { _ = owner1.Delete() }()
-
-		err = testEnv.AwaitEntryReady(e.GetName())
-		Ω(err).ShouldNot(HaveOccurred())
-
-		ownerID2 := "my/owner2"
-		e, err = testEnv.UpdateEntryOwner(e, &ownerID2)
-		Ω(err).ShouldNot(HaveOccurred())
-
-		err = testEnv.AwaitEntryStale(e.GetName())
-		Ω(err).ShouldNot(HaveOccurred())
-
-		err = testEnv.DeleteEntryAndWait(e)
-		Ω(err).ShouldNot(HaveOccurred())
-	})
-
 	It("handles an entry without targets as invalid and can delete it", func() {
 		pr, domain, _, err := testEnv.CreateSecretAndProvider("inmemory.mock", 0)
 		Ω(err).ShouldNot(HaveOccurred())
@@ -174,7 +134,7 @@ var _ = Describe("EntryLivecycle", func() {
 		Ω(err).ShouldNot(HaveOccurred())
 	})
 
-	It("handles entry correctly from ready -> stale -> invalid -> ready", func() {
+	It("handles entry correctly from ready -> invalid -> ready", func() {
 		pr, domain, _, err := testEnv.CreateSecretAndProvider("inmemory.mock", 0)
 		Ω(err).ShouldNot(HaveOccurred())
 
@@ -188,31 +148,12 @@ var _ = Describe("EntryLivecycle", func() {
 
 		checkEntry(e, pr)
 
-		ownerID := "my/owner1"
-		e, err = testEnv.UpdateEntryOwner(e, &ownerID)
-		Ω(err).ShouldNot(HaveOccurred())
-
-		err = testEnv.AwaitEntryStale(e.GetName())
-		Ω(err).ShouldNot(HaveOccurred())
-
-		err = testEnv.MockInMemoryHasEntry(e)
-		Ω(err).ShouldNot(HaveOccurred())
-
 		e, err = testEnv.UpdateEntryTargets(e)
 		Ω(err).ShouldNot(HaveOccurred())
 
-		e, err = testEnv.UpdateEntryOwner(e, nil)
-		Ω(err).ShouldNot(HaveOccurred())
-
 		err = testEnv.AwaitEntryInvalid(e.GetName())
 		Ω(err).ShouldNot(HaveOccurred())
 
-		err = testEnv.Await("entry still in mock provider", func() (bool, error) {
-			err := testEnv.MockInMemoryHasNotEntry(e)
-			return err == nil, err
-		})
-		Ω(err).ShouldNot(HaveOccurred())
-
 		e, err = testEnv.UpdateEntryTargets(e, "1.1.1.1")
 		Ω(err).ShouldNot(HaveOccurred())
 
diff --git a/test/integration/testenv.go b/test/integration/testenv.go
index 337107ff..35d2dad7 100644
--- a/test/integration/testenv.go
+++ b/test/integration/testenv.go
@@ -429,17 +429,6 @@ func (te *TestEnv) CreateEntryGeneric(index int, specSetter EntrySpecSetter) (re
 	return obj, err
 }
 
-func (te *TestEnv) UpdateEntryOwner(obj resources.Object, ownerID *string) (resources.Object, error) {
-	obj, err := te.GetEntry(obj.GetName())
-	if err != nil {
-		return nil, err
-	}
-	e := UnwrapEntry(obj)
-	e.Spec.OwnerId = ownerID
-	err = obj.Update()
-	return obj, err
-}
-
 func (te *TestEnv) UpdateEntryDomain(obj resources.Object, domain string) (resources.Object, error) {
 	obj, err := te.GetEntry(obj.GetName())
 	if err != nil {

From 9b7c977c43476833825bbc9f8758b5d3437b5363 Mon Sep 17 00:00:00 2001
From: Martin Weindel <martin.weindel@sap.com>
Date: Tue, 10 Dec 2024 08:59:44 +0100
Subject: [PATCH 04/10] discontinue section

---
 README.md | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/README.md b/README.md
index 5a97913e..6f73f64b 100644
--- a/README.md
+++ b/README.md
@@ -35,6 +35,7 @@ For extending or adapting this project with your own source or provisioning cont
 
 - [External DNS Management](#external-dns-management)
   - [Index](#index)
+  - [Important Note: Support for owner identifiers is discontinued](#important-note-support-for-owner-identifiers-is-discontinued)
   - [Quick start](#quick-start)
     - [Automatic creation of DNS entries for services and ingresses](#automatic-creation-of-dns-entries-for-services-and-ingresses)
       - [`A` DNS records with alias targets for provider type AWS-Route53 and AWS load balancers](#a-dns-records-with-alias-targets-for-provider-type-aws-route53-and-aws-load-balancers)
@@ -56,6 +57,29 @@ For extending or adapting this project with your own source or provisioning cont
     - [Multiple Cluster Support](#multiple-cluster-support)
   - [Why not use the community `external-dns` solution?](#why-not-use-the-community-external-dns-solution)
 
+## Important Note: Support for owner identifiers is discontinued
+
+Starting with release `v0.23`, the support for owner identifiers is discontinued.
+
+The creation and management of meta data DNS records holding the owner identifier for each `DNSEntry` has been removed.
+These meta data DNS records will be removed automatically. To avoid running into rate limits, this removals will happen
+only during updates or with low batch size during the periodic reconciliation.
+Depending on size of the hosted zone these cleanup can take multiple days.
+To ensure the correct work of the cleanup of these special `TXT` DNS records, the owner identifier provided via 
+the `--identifier` command line option and the `DNSOwner` custom resources need still be provided as before.
+In a future release, the `DNSOwner` resources will be removed completely.
+
+These identifiers are now only used for cleanup, but not for any other purposes.
+
+The ownership information was used to several purposes:
+- detect conflicts (i.e. same DNS record written by multiple dns-controller-manager instances)
+- handing over responsibility of DNS records from one to another controller instance
+- detection of orphan DNS records and their cleanup
+
+Please note that these edge cases are not supported anymore.
+For handing over responsibility of DNS record, please use the `dns.gardener.cloud/ignore=true` annotation 
+on `DNSEntries` or the annotated source objects (like `Ingress`, `Service`, etc.)
+
 ## Quick start
 
 To install the **DNS controller manager** in your Kubernetes cluster, follow these steps.

From 5ad1c702b2af646631d50aa2b4f8a3ac38a12418 Mon Sep 17 00:00:00 2001
From: Martin Weindel <martin.weindel@sap.com>
Date: Tue, 10 Dec 2024 09:19:48 +0100
Subject: [PATCH 05/10] fix lint issues

---
 pkg/dns/provider/changemodel.go | 15 +++++++--------
 pkg/dns/provider/state_zone.go  |  6 +++---
 pkg/dns/utils/target.go         |  2 +-
 pkg/dns/utils/utils_entry.go    |  2 +-
 4 files changed, 12 insertions(+), 13 deletions(-)

diff --git a/pkg/dns/provider/changemodel.go b/pkg/dns/provider/changemodel.go
index 204b249c..def243da 100644
--- a/pkg/dns/provider/changemodel.go
+++ b/pkg/dns/provider/changemodel.go
@@ -9,7 +9,6 @@ import (
 	"reflect"
 	"strconv"
 	"strings"
-	"time"
 
 	api "github.com/gardener/external-dns-management/pkg/apis/dns/v1alpha1"
 	"github.com/gardener/external-dns-management/pkg/dns"
@@ -335,23 +334,23 @@ func (this *ChangeModel) Setup() error {
 	return err
 }
 
-func (this *ChangeModel) Check(name dns.DNSSetName, updateGroup string, createdAt time.Time, done DoneHandler, spec TargetSpec) ChangeResult {
-	return this.Exec(false, false, name, updateGroup, createdAt, done, spec)
+func (this *ChangeModel) Check(name dns.DNSSetName, updateGroup string, done DoneHandler, spec TargetSpec) ChangeResult {
+	return this.Exec(false, false, name, updateGroup, done, spec)
 }
 
-func (this *ChangeModel) Apply(name dns.DNSSetName, updateGroup string, createdAt time.Time, done DoneHandler, spec TargetSpec) ChangeResult {
-	return this.Exec(true, false, name, updateGroup, createdAt, done, spec)
+func (this *ChangeModel) Apply(name dns.DNSSetName, updateGroup string, done DoneHandler, spec TargetSpec) ChangeResult {
+	return this.Exec(true, false, name, updateGroup, done, spec)
 }
 
-func (this *ChangeModel) Delete(name dns.DNSSetName, updateGroup string, createdAt time.Time, done DoneHandler, spec TargetSpec) ChangeResult {
-	return this.Exec(true, true, name, updateGroup, createdAt, done, spec)
+func (this *ChangeModel) Delete(name dns.DNSSetName, updateGroup string, done DoneHandler, spec TargetSpec) ChangeResult {
+	return this.Exec(true, true, name, updateGroup, done, spec)
 }
 
 func (this *ChangeModel) PseudoApply(name dns.DNSSetName, spec TargetSpec) {
 	this.applied[name] = dns.NewDNSSet(name, spec.RoutingPolicy())
 }
 
-func (this *ChangeModel) Exec(apply bool, delete bool, name dns.DNSSetName, updateGroup string, createdAt time.Time, done DoneHandler, spec TargetSpec) ChangeResult {
+func (this *ChangeModel) Exec(apply bool, delete bool, name dns.DNSSetName, updateGroup string, done DoneHandler, spec TargetSpec) ChangeResult {
 	// this.Infof("%s: %v", name, targets)
 	if len(spec.Targets()) == 0 && !delete {
 		return ChangeResult{}
diff --git a/pkg/dns/provider/state_zone.go b/pkg/dns/provider/state_zone.go
index 88973b51..c7eb4e6c 100644
--- a/pkg/dns/provider/state_zone.go
+++ b/pkg/dns/provider/state_zone.go
@@ -174,10 +174,10 @@ func (this *state) reconcileZone(logger logger.LogContext, req *zoneReconciliati
 		spec := e.object.GetTargetSpec(e)
 		statusUpdate := NewStatusUpdate(logger, e, this.GetContext())
 		if e.IsDeleting() {
-			changeResult = changes.Delete(e.DNSSetName(), e.ObjectName().Namespace(), e.CreatedAt(), statusUpdate, spec)
+			changeResult = changes.Delete(e.DNSSetName(), e.ObjectName().Namespace(), statusUpdate, spec)
 		} else {
 			if !e.NotRateLimited() {
-				changeResult = changes.Check(e.DNSSetName(), e.ObjectName().Namespace(), e.CreatedAt(), statusUpdate, spec)
+				changeResult = changes.Check(e.DNSSetName(), e.ObjectName().Namespace(), statusUpdate, spec)
 				if changeResult.Modified {
 					if accepted, delay := this.tryAcceptProviderRateLimiter(logger, e); !accepted {
 						req.zone.nextTrigger = delay
@@ -191,7 +191,7 @@ func (this *state) reconcileZone(logger logger.LogContext, req *zoneReconciliati
 					}
 				}
 			}
-			changeResult = changes.Apply(e.DNSSetName(), e.ObjectName().Namespace(), e.CreatedAt(), statusUpdate, spec)
+			changeResult = changes.Apply(e.DNSSetName(), e.ObjectName().Namespace(), statusUpdate, spec)
 			if changeResult.Error != nil && changeResult.Retry {
 				conflictErr = changeResult.Error
 			}
diff --git a/pkg/dns/utils/target.go b/pkg/dns/utils/target.go
index dbb0e8fd..6eb62209 100644
--- a/pkg/dns/utils/target.go
+++ b/pkg/dns/utils/target.go
@@ -93,7 +93,7 @@ type targetSpec struct {
 	routingPolicy *dns.RoutingPolicy
 }
 
-func BaseTargetSpec(entry *DNSEntryObject, p TargetProvider) TargetSpec {
+func BaseTargetSpec(p TargetProvider) TargetSpec {
 	spec := &targetSpec{
 		targets:       p.Targets(),
 		routingPolicy: p.RoutingPolicy(),
diff --git a/pkg/dns/utils/utils_entry.go b/pkg/dns/utils/utils_entry.go
index d7edaa65..a16c325e 100644
--- a/pkg/dns/utils/utils_entry.go
+++ b/pkg/dns/utils/utils_entry.go
@@ -133,7 +133,7 @@ func (this *DNSEntryObject) AcknowledgeCNAMELookupInterval(interval int64) bool
 }
 
 func (this *DNSEntryObject) GetTargetSpec(p TargetProvider) TargetSpec {
-	return BaseTargetSpec(this, p)
+	return BaseTargetSpec(p)
 }
 
 func DNSSetName(entry *api.DNSEntry) dns.DNSSetName {

From 0584230570efc91654630fa03f35500dfcbda2ed Mon Sep 17 00:00:00 2001
From: Martin Weindel <martin.weindel@sap.com>
Date: Tue, 10 Dec 2024 14:38:49 +0100
Subject: [PATCH 06/10] address feedback from review

---
 README.md                                     | 738 +++++++++---------
 pkg/controller/provider/alicloud/handler.go   |   4 -
 pkg/controller/provider/aws/handler.go        |   4 -
 .../provider/azure-private/handler.go         |   4 -
 pkg/controller/provider/azure/handler.go      |   4 -
 pkg/controller/provider/cloudflare/handler.go |   4 -
 pkg/controller/provider/google/handler.go     |   4 -
 pkg/controller/provider/mock/handler.go       |   4 -
 pkg/controller/provider/netlify/handler.go    |   4 -
 pkg/controller/provider/openstack/handler.go  |   4 -
 pkg/controller/provider/powerdns/handler.go   |   4 -
 pkg/controller/provider/remote/handler.go     |   4 -
 pkg/controller/provider/rfc2136/handler.go    |   4 -
 pkg/dns/dnsset.go                             |  60 +-
 pkg/dns/provider/changemodel.go               |  20 +-
 pkg/dns/provider/interface.go                 |   5 -
 pkg/dns/provider/provider.go                  |   8 -
 pkg/dns/provider/zonecache.go                 |  13 -
 18 files changed, 414 insertions(+), 478 deletions(-)

diff --git a/README.md b/README.md
index 6f73f64b..1264479a 100644
--- a/README.md
+++ b/README.md
@@ -61,17 +61,17 @@ For extending or adapting this project with your own source or provisioning cont
 
 Starting with release `v0.23`, the support for owner identifiers is discontinued.
 
-The creation and management of meta data DNS records holding the owner identifier for each `DNSEntry` has been removed.
-These meta data DNS records will be removed automatically. To avoid running into rate limits, this removals will happen
+The creation and management of metadata DNS records holding the owner identifier for each `DNSEntry` has been removed.
+These metadata DNS records will be removed automatically. To avoid running into rate limits, this removals will happen
 only during updates or with low batch size during the periodic reconciliation.
-Depending on size of the hosted zone these cleanup can take multiple days.
+Depending on the size of the hosted zone the cleanup can take multiple days.
 To ensure the correct work of the cleanup of these special `TXT` DNS records, the owner identifier provided via 
-the `--identifier` command line option and the `DNSOwner` custom resources need still be provided as before.
+the `--identifier` command line option and the `DNSOwner` custom resources must still be provided as before.
 In a future release, the `DNSOwner` resources will be removed completely.
 
 These identifiers are now only used for cleanup, but not for any other purposes.
 
-The ownership information was used to several purposes:
+The ownership information was used for several purposes:
 - detect conflicts (i.e. same DNS record written by multiple dns-controller-manager instances)
 - handing over responsibility of DNS records from one to another controller instance
 - detection of orphan DNS records and their cleanup
@@ -580,366 +580,376 @@ Usage:
   dns-controller-manager [flags]
 
 Flags:
-      --accepted-maintainers string                                   accepted maintainer key(s) for crds
-      --advanced.batch-size int                                       batch size for change requests (currently only used for aws-route53)
-      --advanced.max-retries int                                      maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53)
-      --alicloud-dns.advanced.batch-size int                          batch size for change requests (currently only used for aws-route53)
-      --alicloud-dns.advanced.max-retries int                         maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53)
-      --alicloud-dns.blocked-zone zone-id                             Blocks a zone given in the format zone-id from a provider as if the zone is not existing.
-      --alicloud-dns.ratelimiter.burst int                            number of burst requests for rate limiter
-      --alicloud-dns.ratelimiter.enabled                              enables rate limiter for DNS provider requests
-      --alicloud-dns.ratelimiter.qps int                              maximum requests/queries per second
-      --annotation.default.pool.size int                              Worker pool size for pool default of controller annotation
-      --annotation.pool.size int                                      Worker pool size of controller annotation
-      --annotation.setup int                                          number of processors for controller setup of controller annotation
-      --aws-route53.advanced.batch-size int                           batch size for change requests (currently only used for aws-route53)
-      --aws-route53.advanced.max-retries int                          maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53)
-      --aws-route53.blocked-zone zone-id                              Blocks a zone given in the format zone-id from a provider as if the zone is not existing.
-      --aws-route53.ratelimiter.burst int                             number of burst requests for rate limiter
-      --aws-route53.ratelimiter.enabled                               enables rate limiter for DNS provider requests
-      --aws-route53.ratelimiter.qps int                               maximum requests/queries per second
-      --azure-dns.advanced.batch-size int                             batch size for change requests (currently only used for aws-route53)
-      --azure-dns.advanced.max-retries int                            maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53)
-      --azure-dns.blocked-zone zone-id                                Blocks a zone given in the format zone-id from a provider as if the zone is not existing.
-      --azure-dns.ratelimiter.burst int                               number of burst requests for rate limiter
-      --azure-dns.ratelimiter.enabled                                 enables rate limiter for DNS provider requests
-      --azure-dns.ratelimiter.qps int                                 maximum requests/queries per second
-      --azure-private-dns.advanced.batch-size int                     batch size for change requests (currently only used for aws-route53)
-      --azure-private-dns.advanced.max-retries int                    maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53)
-      --azure-private-dns.blocked-zone zone-id                        Blocks a zone given in the format zone-id from a provider as if the zone is not existing.
-      --azure-private-dns.ratelimiter.burst int                       number of burst requests for rate limiter
-      --azure-private-dns.ratelimiter.enabled                         enables rate limiter for DNS provider requests
-      --azure-private-dns.ratelimiter.qps int                         maximum requests/queries per second
-      --bind-address-http string                                      HTTP server bind address
-      --blocked-zone zone-id                                          Blocks a zone given in the format zone-id from a provider as if the zone is not existing.
-      --cache-ttl int                                                 Time-to-live for provider hosted zone cache
-      --cloudflare-dns.advanced.batch-size int                        batch size for change requests (currently only used for aws-route53)
-      --cloudflare-dns.advanced.max-retries int                       maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53)
-      --cloudflare-dns.blocked-zone zone-id                           Blocks a zone given in the format zone-id from a provider as if the zone is not existing.
-      --cloudflare-dns.ratelimiter.burst int                          number of burst requests for rate limiter
-      --cloudflare-dns.ratelimiter.enabled                            enables rate limiter for DNS provider requests
-      --cloudflare-dns.ratelimiter.qps int                            maximum requests/queries per second
-      --compound.advanced.batch-size int                              batch size for change requests (currently only used for aws-route53) of controller compound
-      --compound.advanced.max-retries int                             maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53) of controller compound
-      --compound.alicloud-dns.advanced.batch-size int                 batch size for change requests (currently only used for aws-route53) of controller compound
-      --compound.alicloud-dns.advanced.max-retries int                maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53) of controller compound
-      --compound.alicloud-dns.blocked-zone zone-id                    Blocks a zone given in the format zone-id from a provider as if the zone is not existing. of controller compound
-      --compound.alicloud-dns.ratelimiter.burst int                   number of burst requests for rate limiter of controller compound
-      --compound.alicloud-dns.ratelimiter.enabled                     enables rate limiter for DNS provider requests of controller compound
-      --compound.alicloud-dns.ratelimiter.qps int                     maximum requests/queries per second of controller compound
-      --compound.aws-route53.advanced.batch-size int                  batch size for change requests (currently only used for aws-route53) of controller compound
-      --compound.aws-route53.advanced.max-retries int                 maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53) of controller compound
-      --compound.aws-route53.blocked-zone zone-id                     Blocks a zone given in the format zone-id from a provider as if the zone is not existing. of controller compound
-      --compound.aws-route53.ratelimiter.burst int                    number of burst requests for rate limiter of controller compound
-      --compound.aws-route53.ratelimiter.enabled                      enables rate limiter for DNS provider requests of controller compound
-      --compound.aws-route53.ratelimiter.qps int                      maximum requests/queries per second of controller compound
-      --compound.azure-dns.advanced.batch-size int                    batch size for change requests (currently only used for aws-route53) of controller compound
-      --compound.azure-dns.advanced.max-retries int                   maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53) of controller compound
-      --compound.azure-dns.blocked-zone zone-id                       Blocks a zone given in the format zone-id from a provider as if the zone is not existing. of controller compound
-      --compound.azure-dns.ratelimiter.burst int                      number of burst requests for rate limiter of controller compound
-      --compound.azure-dns.ratelimiter.enabled                        enables rate limiter for DNS provider requests of controller compound
-      --compound.azure-dns.ratelimiter.qps int                        maximum requests/queries per second of controller compound
-      --compound.azure-private-dns.advanced.batch-size int            batch size for change requests (currently only used for aws-route53) of controller compound
-      --compound.azure-private-dns.advanced.max-retries int           maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53) of controller compound
-      --compound.azure-private-dns.blocked-zone zone-id               Blocks a zone given in the format zone-id from a provider as if the zone is not existing. of controller compound
-      --compound.azure-private-dns.ratelimiter.burst int              number of burst requests for rate limiter of controller compound
-      --compound.azure-private-dns.ratelimiter.enabled                enables rate limiter for DNS provider requests of controller compound
-      --compound.azure-private-dns.ratelimiter.qps int                maximum requests/queries per second of controller compound
-      --compound.blocked-zone zone-id                                 Blocks a zone given in the format zone-id from a provider as if the zone is not existing. of controller compound
-      --compound.cache-ttl int                                        Time-to-live for provider hosted zone cache of controller compound
-      --compound.cloudflare-dns.advanced.batch-size int               batch size for change requests (currently only used for aws-route53) of controller compound
-      --compound.cloudflare-dns.advanced.max-retries int              maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53) of controller compound
-      --compound.cloudflare-dns.blocked-zone zone-id                  Blocks a zone given in the format zone-id from a provider as if the zone is not existing. of controller compound
-      --compound.cloudflare-dns.ratelimiter.burst int                 number of burst requests for rate limiter of controller compound
-      --compound.cloudflare-dns.ratelimiter.enabled                   enables rate limiter for DNS provider requests of controller compound
-      --compound.cloudflare-dns.ratelimiter.qps int                   maximum requests/queries per second of controller compound
-      --compound.default.pool.size int                                Worker pool size for pool default of controller compound
-      --compound.disable-dnsname-validation                           disable validation of domain names according to RFC 1123. of controller compound
-      --compound.disable-zone-state-caching                           disable use of cached dns zone state on changes of controller compound
-      --compound.dns-class string                                     Class identifier used to differentiate responsible controllers for entry resources of controller compound
-      --compound.dns-delay duration                                   delay between two dns reconciliations of controller compound
-      --compound.dns.pool.resync-period duration                      Period for resynchronization for pool dns of controller compound
-      --compound.dns.pool.size int                                    Worker pool size for pool dns of controller compound
-      --compound.dry-run                                              just check, don't modify of controller compound
-      --compound.google-clouddns.advanced.batch-size int              batch size for change requests (currently only used for aws-route53) of controller compound
-      --compound.google-clouddns.advanced.max-retries int             maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53) of controller compound
-      --compound.google-clouddns.blocked-zone zone-id                 Blocks a zone given in the format zone-id from a provider as if the zone is not existing. of controller compound
-      --compound.google-clouddns.ratelimiter.burst int                number of burst requests for rate limiter of controller compound
-      --compound.google-clouddns.ratelimiter.enabled                  enables rate limiter for DNS provider requests of controller compound
-      --compound.google-clouddns.ratelimiter.qps int                  maximum requests/queries per second of controller compound
-      --compound.identifier string                                    Identifier used to mark DNS entries in DNS system of controller compound
-      --compound.infoblox-dns.advanced.batch-size int                 batch size for change requests (currently only used for aws-route53) of controller compound
-      --compound.infoblox-dns.advanced.max-retries int                maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53) of controller compound
-      --compound.infoblox-dns.blocked-zone zone-id                    Blocks a zone given in the format zone-id from a provider as if the zone is not existing. of controller compound
-      --compound.infoblox-dns.ratelimiter.burst int                   number of burst requests for rate limiter of controller compound
-      --compound.infoblox-dns.ratelimiter.enabled                     enables rate limiter for DNS provider requests of controller compound
-      --compound.infoblox-dns.ratelimiter.qps int                     maximum requests/queries per second of controller compound
-      --compound.lock-status-check-period duration                    interval for dns lock status checks of controller compound
+      --accepted-maintainers string                                     accepted maintainer key(s) for crds
+      --advanced.batch-size int                                         batch size for change requests (currently only used for aws-route53)
+      --advanced.max-retries int                                        maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53)
+      --alicloud-dns.advanced.batch-size int                            batch size for change requests (currently only used for aws-route53)
+      --alicloud-dns.advanced.max-retries int                           maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53)
+      --alicloud-dns.blocked-zone zone-id                               Blocks a zone given in the format zone-id from a provider as if the zone is not existing.
+      --alicloud-dns.ratelimiter.burst int                              number of burst requests for rate limiter
+      --alicloud-dns.ratelimiter.enabled                                enables rate limiter for DNS provider requests
+      --alicloud-dns.ratelimiter.qps int                                maximum requests/queries per second
+      --annotation.default.pool.size int                                Worker pool size for pool default of controller annotation
+      --annotation.pool.size int                                        Worker pool size of controller annotation
+      --annotation.setup int                                            number of processors for controller setup of controller annotation
+      --aws-route53.advanced.batch-size int                             batch size for change requests (currently only used for aws-route53)
+      --aws-route53.advanced.max-retries int                            maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53)
+      --aws-route53.blocked-zone zone-id                                Blocks a zone given in the format zone-id from a provider as if the zone is not existing.
+      --aws-route53.ratelimiter.burst int                               number of burst requests for rate limiter
+      --aws-route53.ratelimiter.enabled                                 enables rate limiter for DNS provider requests
+      --aws-route53.ratelimiter.qps int                                 maximum requests/queries per second
+      --azure-dns.advanced.batch-size int                               batch size for change requests (currently only used for aws-route53)
+      --azure-dns.advanced.max-retries int                              maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53)
+      --azure-dns.blocked-zone zone-id                                  Blocks a zone given in the format zone-id from a provider as if the zone is not existing.
+      --azure-dns.ratelimiter.burst int                                 number of burst requests for rate limiter
+      --azure-dns.ratelimiter.enabled                                   enables rate limiter for DNS provider requests
+      --azure-dns.ratelimiter.qps int                                   maximum requests/queries per second
+      --azure-private-dns.advanced.batch-size int                       batch size for change requests (currently only used for aws-route53)
+      --azure-private-dns.advanced.max-retries int                      maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53)
+      --azure-private-dns.blocked-zone zone-id                          Blocks a zone given in the format zone-id from a provider as if the zone is not existing.
+      --azure-private-dns.ratelimiter.burst int                         number of burst requests for rate limiter
+      --azure-private-dns.ratelimiter.enabled                           enables rate limiter for DNS provider requests
+      --azure-private-dns.ratelimiter.qps int                           maximum requests/queries per second
+      --bind-address-http string                                        HTTP server bind address
+      --blocked-zone zone-id                                            Blocks a zone given in the format zone-id from a provider as if the zone is not existing.
+      --cache-ttl int                                                   Time-to-live for provider hosted zone cache
+      --cloudflare-dns.advanced.batch-size int                          batch size for change requests (currently only used for aws-route53)
+      --cloudflare-dns.advanced.max-retries int                         maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53)
+      --cloudflare-dns.blocked-zone zone-id                             Blocks a zone given in the format zone-id from a provider as if the zone is not existing.
+      --cloudflare-dns.ratelimiter.burst int                            number of burst requests for rate limiter
+      --cloudflare-dns.ratelimiter.enabled                              enables rate limiter for DNS provider requests
+      --cloudflare-dns.ratelimiter.qps int                              maximum requests/queries per second
+      --compound.advanced.batch-size int                                batch size for change requests (currently only used for aws-route53) of controller compound
+      --compound.advanced.max-retries int                               maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53) of controller compound
+      --compound.alicloud-dns.advanced.batch-size int                   batch size for change requests (currently only used for aws-route53) of controller compound
+      --compound.alicloud-dns.advanced.max-retries int                  maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53) of controller compound
+      --compound.alicloud-dns.blocked-zone zone-id                      Blocks a zone given in the format zone-id from a provider as if the zone is not existing. of controller compound
+      --compound.alicloud-dns.ratelimiter.burst int                     number of burst requests for rate limiter of controller compound
+      --compound.alicloud-dns.ratelimiter.enabled                       enables rate limiter for DNS provider requests of controller compound
+      --compound.alicloud-dns.ratelimiter.qps int                       maximum requests/queries per second of controller compound
+      --compound.aws-route53.advanced.batch-size int                    batch size for change requests (currently only used for aws-route53) of controller compound
+      --compound.aws-route53.advanced.max-retries int                   maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53) of controller compound
+      --compound.aws-route53.blocked-zone zone-id                       Blocks a zone given in the format zone-id from a provider as if the zone is not existing. of controller compound
+      --compound.aws-route53.ratelimiter.burst int                      number of burst requests for rate limiter of controller compound
+      --compound.aws-route53.ratelimiter.enabled                        enables rate limiter for DNS provider requests of controller compound
+      --compound.aws-route53.ratelimiter.qps int                        maximum requests/queries per second of controller compound
+      --compound.azure-dns.advanced.batch-size int                      batch size for change requests (currently only used for aws-route53) of controller compound
+      --compound.azure-dns.advanced.max-retries int                     maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53) of controller compound
+      --compound.azure-dns.blocked-zone zone-id                         Blocks a zone given in the format zone-id from a provider as if the zone is not existing. of controller compound
+      --compound.azure-dns.ratelimiter.burst int                        number of burst requests for rate limiter of controller compound
+      --compound.azure-dns.ratelimiter.enabled                          enables rate limiter for DNS provider requests of controller compound
+      --compound.azure-dns.ratelimiter.qps int                          maximum requests/queries per second of controller compound
+      --compound.azure-private-dns.advanced.batch-size int              batch size for change requests (currently only used for aws-route53) of controller compound
+      --compound.azure-private-dns.advanced.max-retries int             maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53) of controller compound
+      --compound.azure-private-dns.blocked-zone zone-id                 Blocks a zone given in the format zone-id from a provider as if the zone is not existing. of controller compound
+      --compound.azure-private-dns.ratelimiter.burst int                number of burst requests for rate limiter of controller compound
+      --compound.azure-private-dns.ratelimiter.enabled                  enables rate limiter for DNS provider requests of controller compound
+      --compound.azure-private-dns.ratelimiter.qps int                  maximum requests/queries per second of controller compound
+      --compound.blocked-zone zone-id                                   Blocks a zone given in the format zone-id from a provider as if the zone is not existing. of controller compound
+      --compound.cache-ttl int                                          Time-to-live for provider hosted zone cache of controller compound
+      --compound.cloudflare-dns.advanced.batch-size int                 batch size for change requests (currently only used for aws-route53) of controller compound
+      --compound.cloudflare-dns.advanced.max-retries int                maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53) of controller compound
+      --compound.cloudflare-dns.blocked-zone zone-id                    Blocks a zone given in the format zone-id from a provider as if the zone is not existing. of controller compound
+      --compound.cloudflare-dns.ratelimiter.burst int                   number of burst requests for rate limiter of controller compound
+      --compound.cloudflare-dns.ratelimiter.enabled                     enables rate limiter for DNS provider requests of controller compound
+      --compound.cloudflare-dns.ratelimiter.qps int                     maximum requests/queries per second of controller compound
+      --compound.default.pool.size int                                  Worker pool size for pool default of controller compound
+      --compound.disable-dnsname-validation                             disable validation of domain names according to RFC 1123. of controller compound
+      --compound.disable-zone-state-caching                             disable use of cached dns zone state on changes of controller compound
+      --compound.dns-class string                                       Class identifier used to differentiate responsible controllers for entry resources of controller compound
+      --compound.dns-delay duration                                     delay between two dns reconciliations of controller compound
+      --compound.dns.pool.resync-period duration                        Period for resynchronization for pool dns of controller compound
+      --compound.dns.pool.size int                                      Worker pool size for pool dns of controller compound
+      --compound.dry-run                                                just check, don't modify of controller compound
+      --compound.google-clouddns.advanced.batch-size int                batch size for change requests (currently only used for aws-route53) of controller compound
+      --compound.google-clouddns.advanced.max-retries int               maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53) of controller compound
+      --compound.google-clouddns.blocked-zone zone-id                   Blocks a zone given in the format zone-id from a provider as if the zone is not existing. of controller compound
+      --compound.google-clouddns.ratelimiter.burst int                  number of burst requests for rate limiter of controller compound
+      --compound.google-clouddns.ratelimiter.enabled                    enables rate limiter for DNS provider requests of controller compound
+      --compound.google-clouddns.ratelimiter.qps int                    maximum requests/queries per second of controller compound
+      --compound.identifier string                                      Identifier used to mark DNS entries in DNS system of controller compound
+      --compound.infoblox-dns.advanced.batch-size int                   batch size for change requests (currently only used for aws-route53) of controller compound
+      --compound.infoblox-dns.advanced.max-retries int                  maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53) of controller compound
+      --compound.infoblox-dns.blocked-zone zone-id                      Blocks a zone given in the format zone-id from a provider as if the zone is not existing. of controller compound
+      --compound.infoblox-dns.ratelimiter.burst int                     number of burst requests for rate limiter of controller compound
+      --compound.infoblox-dns.ratelimiter.enabled                       enables rate limiter for DNS provider requests of controller compound
+      --compound.infoblox-dns.ratelimiter.qps int                       maximum requests/queries per second of controller compound
+      --compound.lock-status-check-period duration                      interval for dns lock status checks of controller compound
       --compound.max-metadata-record-deletions-per-reconciliation int   maximum number of metadata owner records that can be deleted per zone reconciliation of controller compound
-      --compound.netlify-dns.advanced.batch-size int                  batch size for change requests (currently only used for aws-route53) of controller compound
-      --compound.netlify-dns.advanced.max-retries int                 maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53) of controller compound
-      --compound.netlify-dns.blocked-zone zone-id                     Blocks a zone given in the format zone-id from a provider as if the zone is not existing. of controller compound
-      --compound.netlify-dns.ratelimiter.burst int                    number of burst requests for rate limiter of controller compound
-      --compound.netlify-dns.ratelimiter.enabled                      enables rate limiter for DNS provider requests of controller compound
-      --compound.netlify-dns.ratelimiter.qps int                      maximum requests/queries per second of controller compound
-      --compound.openstack-designate.advanced.batch-size int          batch size for change requests (currently only used for aws-route53) of controller compound
-      --compound.openstack-designate.advanced.max-retries int         maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53) of controller compound
-      --compound.openstack-designate.blocked-zone zone-id             Blocks a zone given in the format zone-id from a provider as if the zone is not existing. of controller compound
-      --compound.openstack-designate.ratelimiter.burst int            number of burst requests for rate limiter of controller compound
-      --compound.openstack-designate.ratelimiter.enabled              enables rate limiter for DNS provider requests of controller compound
-      --compound.openstack-designate.ratelimiter.qps int              maximum requests/queries per second of controller compound
-      --compound.ownerids.pool.size int                               Worker pool size for pool ownerids of controller compound
-      --compound.pool.resync-period duration                          Period for resynchronization of controller compound
-      --compound.pool.size int                                        Worker pool size of controller compound
-      --compound.provider-types string                                comma separated list of provider types to enable of controller compound
-      --compound.providers.pool.resync-period duration                Period for resynchronization for pool providers of controller compound
-      --compound.providers.pool.size int                              Worker pool size for pool providers of controller compound
-      --compound.ratelimiter.burst int                                number of burst requests for rate limiter of controller compound
-      --compound.ratelimiter.enabled                                  enables rate limiter for DNS provider requests of controller compound
-      --compound.ratelimiter.qps int                                  maximum requests/queries per second of controller compound
-      --compound.remote-access-cacert string                          CA who signed client certs file of controller compound
-      --compound.remote-access-client-id string                       identifier used for remote access of controller compound
-      --compound.remote-access-port int                               port of remote access server for remote-enabled providers of controller compound
-      --compound.remote-access-server-secret-name string              name of secret containing remote access server's certificate of controller compound
-      --compound.remote.advanced.batch-size int                       batch size for change requests (currently only used for aws-route53) of controller compound
-      --compound.remote.advanced.max-retries int                      maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53) of controller compound
-      --compound.remote.blocked-zone zone-id                          Blocks a zone given in the format zone-id from a provider as if the zone is not existing. of controller compound
-      --compound.remote.ratelimiter.burst int                         number of burst requests for rate limiter of controller compound
-      --compound.remote.ratelimiter.enabled                           enables rate limiter for DNS provider requests of controller compound
-      --compound.remote.ratelimiter.qps int                           maximum requests/queries per second of controller compound
-      --compound.reschedule-delay duration                            reschedule delay after losing provider of controller compound
-      --compound.rfc2136.advanced.batch-size int                      batch size for change requests (currently only used for aws-route53) of controller compound
-      --compound.rfc2136.advanced.max-retries int                     maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53) of controller compound
-      --compound.rfc2136.blocked-zone zone-id                         Blocks a zone given in the format zone-id from a provider as if the zone is not existing. of controller compound
-      --compound.rfc2136.ratelimiter.burst int                        number of burst requests for rate limiter of controller compound
-      --compound.rfc2136.ratelimiter.enabled                          enables rate limiter for DNS provider requests of controller compound
-      --compound.rfc2136.ratelimiter.qps int                          maximum requests/queries per second of controller compound
-      --compound.secrets.pool.size int                                Worker pool size for pool secrets of controller compound
-      --compound.setup int                                            number of processors for controller setup of controller compound
-      --compound.ttl int                                              Default time-to-live for DNS entries. Defines how long the record is kept in cache by DNS servers or resolvers. of controller compound
-      --compound.zonepolicies.pool.size int                           Worker pool size for pool zonepolicies of controller compound
-      --config string                                                 config file
-  -c, --controllers string                                            comma separated list of controllers to start (<name>,<group>,all)
-      --cpuprofile string                                             set file for cpu profiling
-      --default.pool.resync-period duration                           Period for resynchronization for pool default
-      --default.pool.size int                                         Worker pool size for pool default
-      --disable-dnsname-validation                                    disable validation of domain names according to RFC 1123.
-      --disable-namespace-restriction                                 disable access restriction for namespace local access only
-      --disable-zone-state-caching                                    disable use of cached dns zone state on changes
-      --dns-class string                                              identifier used to differentiate responsible controllers for providers, identifier used to differentiate responsible controllers for entries, Class identifier used to differentiate responsible controllers for entry resources
-      --dns-delay duration                                            delay between two dns reconciliations
-      --dns-target-class string                                       identifier used to differentiate responsible dns controllers for target providers, identifier used to differentiate responsible dns controllers for target entries
-      --dns.pool.resync-period duration                               Period for resynchronization for pool dns
-      --dns.pool.size int                                             Worker pool size for pool dns
-      --dnsentry-source.default.pool.resync-period duration           Period for resynchronization for pool default of controller dnsentry-source
-      --dnsentry-source.default.pool.size int                         Worker pool size for pool default of controller dnsentry-source
-      --dnsentry-source.dns-class string                              identifier used to differentiate responsible controllers for entries of controller dnsentry-source
-      --dnsentry-source.dns-target-class string                       identifier used to differentiate responsible dns controllers for target entries of controller dnsentry-source
-      --dnsentry-source.exclude-domains stringArray                   excluded domains of controller dnsentry-source
-      --dnsentry-source.key string                                    selecting key for annotation of controller dnsentry-source
-      --dnsentry-source.pool.resync-period duration                   Period for resynchronization of controller dnsentry-source
-      --dnsentry-source.pool.size int                                 Worker pool size of controller dnsentry-source
-      --dnsentry-source.target-creator-label-name string              label name to store the creator for generated DNS entries of controller dnsentry-source
-      --dnsentry-source.target-creator-label-value string             label value for creator label of controller dnsentry-source
-      --dnsentry-source.target-name-prefix string                     name prefix in target namespace for cross cluster generation of controller dnsentry-source
-      --dnsentry-source.target-namespace string                       target namespace for cross cluster generation of controller dnsentry-source
-      --dnsentry-source.target-owner-id string                        owner id to use for generated DNS entries of controller dnsentry-source
-      --dnsentry-source.target-owner-object string                    owner object to use for generated DNS entries of controller dnsentry-source
-      --dnsentry-source.target-realms string                          realm(s) to use for generated DNS entries of controller dnsentry-source
-      --dnsentry-source.target-set-ignore-owners                      mark generated DNS entries to omit owner based access control of controller dnsentry-source
-      --dnsentry-source.targets.pool.size int                         Worker pool size for pool targets of controller dnsentry-source
-      --dnsprovider-replication.default.pool.resync-period duration   Period for resynchronization for pool default of controller dnsprovider-replication
-      --dnsprovider-replication.default.pool.size int                 Worker pool size for pool default of controller dnsprovider-replication
-      --dnsprovider-replication.dns-class string                      identifier used to differentiate responsible controllers for providers of controller dnsprovider-replication
-      --dnsprovider-replication.dns-target-class string               identifier used to differentiate responsible dns controllers for target providers of controller dnsprovider-replication
-      --dnsprovider-replication.pool.resync-period duration           Period for resynchronization of controller dnsprovider-replication
-      --dnsprovider-replication.pool.size int                         Worker pool size of controller dnsprovider-replication
-      --dnsprovider-replication.target-creator-label-name string      label name to store the creator for replicated DNS providers of controller dnsprovider-replication
-      --dnsprovider-replication.target-creator-label-value string     label value for creator label of controller dnsprovider-replication
-      --dnsprovider-replication.target-name-prefix string             name prefix in target namespace for cross cluster replication of controller dnsprovider-replication
-      --dnsprovider-replication.target-namespace string               target namespace for cross cluster generation of controller dnsprovider-replication
-      --dnsprovider-replication.target-realms string                  realm(s) to use for replicated DNS provider of controller dnsprovider-replication
-      --dnsprovider-replication.targets.pool.size int                 Worker pool size for pool targets of controller dnsprovider-replication
-      --dry-run                                                       just check, don't modify
-      --enable-profiling                                              enables profiling server at path /debug/pprof (needs option --server-port-http)
-      --exclude-domains stringArray                                   excluded domains
-      --force-crd-update                                              enforce update of crds even they are unmanaged
-      --google-clouddns.advanced.batch-size int                       batch size for change requests (currently only used for aws-route53)
-      --google-clouddns.advanced.max-retries int                      maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53)
-      --google-clouddns.blocked-zone zone-id                          Blocks a zone given in the format zone-id from a provider as if the zone is not existing.
-      --google-clouddns.ratelimiter.burst int                         number of burst requests for rate limiter
-      --google-clouddns.ratelimiter.enabled                           enables rate limiter for DNS provider requests
-      --google-clouddns.ratelimiter.qps int                           maximum requests/queries per second
-      --grace-period duration                                         inactivity grace period for detecting end of cleanup for shutdown
-  -h, --help                                                          help for dns-controller-manager
-      --httproutes.pool.size int                                      Worker pool size for pool httproutes
-      --identifier string                                             Identifier used to mark DNS entries in DNS system
-      --infoblox-dns.advanced.batch-size int                          batch size for change requests (currently only used for aws-route53)
-      --infoblox-dns.advanced.max-retries int                         maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53)
-      --infoblox-dns.blocked-zone zone-id                             Blocks a zone given in the format zone-id from a provider as if the zone is not existing.
-      --infoblox-dns.ratelimiter.burst int                            number of burst requests for rate limiter
-      --infoblox-dns.ratelimiter.enabled                              enables rate limiter for DNS provider requests
-      --infoblox-dns.ratelimiter.qps int                              maximum requests/queries per second
-      --ingress-dns.default.pool.resync-period duration               Period for resynchronization for pool default of controller ingress-dns
-      --ingress-dns.default.pool.size int                             Worker pool size for pool default of controller ingress-dns
-      --ingress-dns.dns-class string                                  identifier used to differentiate responsible controllers for entries of controller ingress-dns
-      --ingress-dns.dns-target-class string                           identifier used to differentiate responsible dns controllers for target entries of controller ingress-dns
-      --ingress-dns.exclude-domains stringArray                       excluded domains of controller ingress-dns
-      --ingress-dns.key string                                        selecting key for annotation of controller ingress-dns
-      --ingress-dns.pool.resync-period duration                       Period for resynchronization of controller ingress-dns
-      --ingress-dns.pool.size int                                     Worker pool size of controller ingress-dns
-      --ingress-dns.target-creator-label-name string                  label name to store the creator for generated DNS entries of controller ingress-dns
-      --ingress-dns.target-creator-label-value string                 label value for creator label of controller ingress-dns
-      --ingress-dns.target-name-prefix string                         name prefix in target namespace for cross cluster generation of controller ingress-dns
-      --ingress-dns.target-namespace string                           target namespace for cross cluster generation of controller ingress-dns
-      --ingress-dns.target-owner-id string                            owner id to use for generated DNS entries of controller ingress-dns
-      --ingress-dns.target-owner-object string                        owner object to use for generated DNS entries of controller ingress-dns
-      --ingress-dns.target-realms string                              realm(s) to use for generated DNS entries of controller ingress-dns
-      --ingress-dns.target-set-ignore-owners                          mark generated DNS entries to omit owner based access control of controller ingress-dns
-      --ingress-dns.targets.pool.size int                             Worker pool size for pool targets of controller ingress-dns
-      --istio-gateways-dns.default.pool.resync-period duration        Period for resynchronization for pool default of controller istio-gateways-dns
-      --istio-gateways-dns.default.pool.size int                      Worker pool size for pool default of controller istio-gateways-dns
-      --istio-gateways-dns.dns-class string                           identifier used to differentiate responsible controllers for entries of controller istio-gateways-dns
-      --istio-gateways-dns.dns-target-class string                    identifier used to differentiate responsible dns controllers for target entries of controller istio-gateways-dns
-      --istio-gateways-dns.exclude-domains stringArray                excluded domains of controller istio-gateways-dns
-      --istio-gateways-dns.key string                                 selecting key for annotation of controller istio-gateways-dns
-      --istio-gateways-dns.pool.resync-period duration                Period for resynchronization of controller istio-gateways-dns
-      --istio-gateways-dns.pool.size int                              Worker pool size of controller istio-gateways-dns
-      --istio-gateways-dns.target-creator-label-name string           label name to store the creator for generated DNS entries of controller istio-gateways-dns
-      --istio-gateways-dns.target-creator-label-value string          label value for creator label of controller istio-gateways-dns
-      --istio-gateways-dns.target-name-prefix string                  name prefix in target namespace for cross cluster generation of controller istio-gateways-dns
-      --istio-gateways-dns.target-namespace string                    target namespace for cross cluster generation of controller istio-gateways-dns
-      --istio-gateways-dns.target-owner-id string                     owner id to use for generated DNS entries of controller istio-gateways-dns
-      --istio-gateways-dns.target-owner-object string                 owner object to use for generated DNS entries of controller istio-gateways-dns
-      --istio-gateways-dns.target-realms string                       realm(s) to use for generated DNS entries of controller istio-gateways-dns
-      --istio-gateways-dns.target-set-ignore-owners                   mark generated DNS entries to omit owner based access control of controller istio-gateways-dns
-      --istio-gateways-dns.targets.pool.size int                      Worker pool size for pool targets of controller istio-gateways-dns
-      --istio-gateways-dns.targetsources.pool.size int                Worker pool size for pool targetsources of controller istio-gateways-dns
-      --istio-gateways-dns.virtualservices.pool.size int              Worker pool size for pool virtualservices of controller istio-gateways-dns
-      --k8s-gateways-dns.default.pool.resync-period duration          Period for resynchronization for pool default of controller k8s-gateways-dns
-      --k8s-gateways-dns.default.pool.size int                        Worker pool size for pool default of controller k8s-gateways-dns
-      --k8s-gateways-dns.dns-class string                             identifier used to differentiate responsible controllers for entries of controller k8s-gateways-dns
-      --k8s-gateways-dns.dns-target-class string                      identifier used to differentiate responsible dns controllers for target entries of controller k8s-gateways-dns
-      --k8s-gateways-dns.exclude-domains stringArray                  excluded domains of controller k8s-gateways-dns
-      --k8s-gateways-dns.httproutes.pool.size int                     Worker pool size for pool httproutes of controller k8s-gateways-dns
-      --k8s-gateways-dns.key string                                   selecting key for annotation of controller k8s-gateways-dns
-      --k8s-gateways-dns.pool.resync-period duration                  Period for resynchronization of controller k8s-gateways-dns
-      --k8s-gateways-dns.pool.size int                                Worker pool size of controller k8s-gateways-dns
-      --k8s-gateways-dns.target-creator-label-name string             label name to store the creator for generated DNS entries of controller k8s-gateways-dns
-      --k8s-gateways-dns.target-creator-label-value string            label value for creator label of controller k8s-gateways-dns
-      --k8s-gateways-dns.target-name-prefix string                    name prefix in target namespace for cross cluster generation of controller k8s-gateways-dns
-      --k8s-gateways-dns.target-namespace string                      target namespace for cross cluster generation of controller k8s-gateways-dns
-      --k8s-gateways-dns.target-owner-id string                       owner id to use for generated DNS entries of controller k8s-gateways-dns
-      --k8s-gateways-dns.target-owner-object string                   owner object to use for generated DNS entries of controller k8s-gateways-dns
-      --k8s-gateways-dns.target-realms string                         realm(s) to use for generated DNS entries of controller k8s-gateways-dns
-      --k8s-gateways-dns.target-set-ignore-owners                     mark generated DNS entries to omit owner based access control of controller k8s-gateways-dns
-      --k8s-gateways-dns.targets.pool.size int                        Worker pool size for pool targets of controller k8s-gateways-dns
-      --key string                                                    selecting key for annotation
-      --kubeconfig string                                             default cluster access
-      --kubeconfig.disable-deploy-crds                                disable deployment of required crds for cluster default
-      --kubeconfig.id string                                          id for cluster default
-      --kubeconfig.migration-ids string                               migration id for cluster default
-      --lease-duration duration                                       lease duration
-      --lease-name string                                             name for lease object
-      --lease-renew-deadline duration                                 lease renew deadline
-      --lease-resource-lock string                                    determines which resource lock to use for leader election, defaults to 'leases'
-      --lease-retry-period duration                                   lease retry period
-      --lock-status-check-period duration                             interval for dns lock status checks
-  -D, --log-level string                                              logrus log level
-      --maintainer string                                             maintainer key for crds (default "dns-controller-manager")
-      --max-metadata-record-deletions-per-reconciliation int          maximum number of metadata owner records that can be deleted per zone reconciliation      
-      --name string                                                   name used for controller manager (default "dns-controller-manager")
-      --namespace string                                              namespace for lease (default "kube-system")
-  -n, --namespace-local-access-only                                   enable access restriction for namespace local access only (deprecated)
-      --netlify-dns.advanced.batch-size int                           batch size for change requests (currently only used for aws-route53)
-      --netlify-dns.advanced.max-retries int                          maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53)
-      --netlify-dns.blocked-zone zone-id                              Blocks a zone given in the format zone-id from a provider as if the zone is not existing.
-      --netlify-dns.ratelimiter.burst int                             number of burst requests for rate limiter
-      --netlify-dns.ratelimiter.enabled                               enables rate limiter for DNS provider requests
-      --netlify-dns.ratelimiter.qps int                               maximum requests/queries per second
-      --omit-lease                                                    omit lease for development
-      --openstack-designate.advanced.batch-size int                   batch size for change requests (currently only used for aws-route53)
-      --openstack-designate.advanced.max-retries int                  maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53)
-      --openstack-designate.blocked-zone zone-id                      Blocks a zone given in the format zone-id from a provider as if the zone is not existing.
-      --openstack-designate.ratelimiter.burst int                     number of burst requests for rate limiter
-      --openstack-designate.ratelimiter.enabled                       enables rate limiter for DNS provider requests
-      --openstack-designate.ratelimiter.qps int                       maximum requests/queries per second
-      --ownerids.pool.size int                                        Worker pool size for pool ownerids
-      --plugin-file string                                            directory containing go plugins
-      --pool.resync-period duration                                   Period for resynchronization
-      --pool.size int                                                 Worker pool size
-      --provider-types string                                         comma separated list of provider types to enable
-      --providers string                                              cluster to look for provider objects
-      --providers.disable-deploy-crds                                 disable deployment of required crds for cluster provider
-      --providers.id string                                           id for cluster provider
-      --providers.migration-ids string                                migration id for cluster provider
-      --providers.pool.resync-period duration                         Period for resynchronization for pool providers
-      --providers.pool.size int                                       Worker pool size for pool providers
-      --ratelimiter.burst int                                         number of burst requests for rate limiter
-      --ratelimiter.enabled                                           enables rate limiter for DNS provider requests
-      --ratelimiter.qps int                                           maximum requests/queries per second
-      --remote-access-cacert string                                   filename for certificate of client CA, CA who signed client certs file
-      --remote-access-cakey string                                    filename for private key of client CA
-      --remote-access-client-id string                                identifier used for remote access
-      --remote-access-port int                                        port of remote access server for remote-enabled providers
-      --remote-access-server-secret-name string                       name of secret containing remote access server's certificate
-      --remote.advanced.batch-size int                                batch size for change requests (currently only used for aws-route53)
-      --remote.advanced.max-retries int                               maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53)
-      --remote.blocked-zone zone-id                                   Blocks a zone given in the format zone-id from a provider as if the zone is not existing.
-      --remote.ratelimiter.burst int                                  number of burst requests for rate limiter
-      --remote.ratelimiter.enabled                                    enables rate limiter for DNS provider requests
-      --remote.ratelimiter.qps int                                    maximum requests/queries per second
-      --remoteaccesscertificates.default.pool.size int                Worker pool size for pool default of controller remoteaccesscertificates
-      --remoteaccesscertificates.pool.size int                        Worker pool size of controller remoteaccesscertificates
-      --remoteaccesscertificates.remote-access-cacert string          filename for certificate of client CA of controller remoteaccesscertificates
-      --remoteaccesscertificates.remote-access-cakey string           filename for private key of client CA of controller remoteaccesscertificates
-      --reschedule-delay duration                                     reschedule delay after losing provider
-      --rfc2136.advanced.batch-size int                               batch size for change requests (currently only used for aws-route53)
-      --rfc2136.advanced.max-retries int                              maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53)
-      --rfc2136.blocked-zone zone-id                                  Blocks a zone given in the format zone-id from a provider as if the zone is not existing.
-      --rfc2136.ratelimiter.burst int                                 number of burst requests for rate limiter
-      --rfc2136.ratelimiter.enabled                                   enables rate limiter for DNS provider requests
-      --rfc2136.ratelimiter.qps int                                   maximum requests/queries per second
-      --secrets.pool.size int                                         Worker pool size for pool secrets
-      --server-port-http int                                          HTTP server port (serving /healthz, /metrics, ...)
-      --service-dns.default.pool.resync-period duration               Period for resynchronization for pool default of controller service-dns
-      --service-dns.default.pool.size int                             Worker pool size for pool default of controller service-dns
-      --service-dns.dns-class string                                  identifier used to differentiate responsible controllers for entries of controller service-dns
-      --service-dns.dns-target-class string                           identifier used to differentiate responsible dns controllers for target entries of controller service-dns
-      --service-dns.exclude-domains stringArray                       excluded domains of controller service-dns
-      --service-dns.key string                                        selecting key for annotation of controller service-dns
-      --service-dns.pool.resync-period duration                       Period for resynchronization of controller service-dns
-      --service-dns.pool.size int                                     Worker pool size of controller service-dns
-      --service-dns.target-creator-label-name string                  label name to store the creator for generated DNS entries of controller service-dns
-      --service-dns.target-creator-label-value string                 label value for creator label of controller service-dns
-      --service-dns.target-name-prefix string                         name prefix in target namespace for cross cluster generation of controller service-dns
-      --service-dns.target-namespace string                           target namespace for cross cluster generation of controller service-dns
-      --service-dns.target-owner-id string                            owner id to use for generated DNS entries of controller service-dns
-      --service-dns.target-owner-object string                        owner object to use for generated DNS entries of controller service-dns
-      --service-dns.target-realms string                              realm(s) to use for generated DNS entries of controller service-dns
-      --service-dns.target-set-ignore-owners                          mark generated DNS entries to omit owner based access control of controller service-dns
-      --service-dns.targets.pool.size int                             Worker pool size for pool targets of controller service-dns
-      --setup int                                                     number of processors for controller setup
-      --target string                                                 target cluster for dns requests
-      --target-creator-label-name string                              label name to store the creator for replicated DNS providers, label name to store the creator for generated DNS entries
-      --target-creator-label-value string                             label value for creator label
-      --target-name-prefix string                                     name prefix in target namespace for cross cluster replication, name prefix in target namespace for cross cluster generation
-      --target-namespace string                                       target namespace for cross cluster generation
-      --target-owner-id string                                        owner id to use for generated DNS entries
-      --target-owner-object string                                    owner object to use for generated DNS entries
-      --target-realms string                                          realm(s) to use for replicated DNS provider, realm(s) to use for generated DNS entries
-      --target-set-ignore-owners                                      mark generated DNS entries to omit owner based access control
-      --target.disable-deploy-crds                                    disable deployment of required crds for cluster target
-      --target.id string                                              id for cluster target
-      --target.migration-ids string                                   migration id for cluster target
-      --targets.pool.size int                                         Worker pool size for pool targets
-      --targetsources.pool.size int                                   Worker pool size for pool targetsources
-      --ttl int                                                       Default time-to-live for DNS entries. Defines how long the record is kept in cache by DNS servers or resolvers.
-  -v, --version                                                       version for dns-controller-manager
-      --virtualservices.pool.size int                                 Worker pool size for pool virtualservices
-      --watch-gateways-crds.default.pool.size int                     Worker pool size for pool default of controller watch-gateways-crds
-      --watch-gateways-crds.pool.size int                             Worker pool size of controller watch-gateways-crds
-      --zonepolicies.pool.size int                                    Worker pool size for pool zonepolicies
+      --compound.netlify-dns.advanced.batch-size int                    batch size for change requests (currently only used for aws-route53) of controller compound
+      --compound.netlify-dns.advanced.max-retries int                   maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53) of controller compound
+      --compound.netlify-dns.blocked-zone zone-id                       Blocks a zone given in the format zone-id from a provider as if the zone is not existing. of controller compound
+      --compound.netlify-dns.ratelimiter.burst int                      number of burst requests for rate limiter of controller compound
+      --compound.netlify-dns.ratelimiter.enabled                        enables rate limiter for DNS provider requests of controller compound
+      --compound.netlify-dns.ratelimiter.qps int                        maximum requests/queries per second of controller compound
+      --compound.openstack-designate.advanced.batch-size int            batch size for change requests (currently only used for aws-route53) of controller compound
+      --compound.openstack-designate.advanced.max-retries int           maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53) of controller compound
+      --compound.openstack-designate.blocked-zone zone-id               Blocks a zone given in the format zone-id from a provider as if the zone is not existing. of controller compound
+      --compound.openstack-designate.ratelimiter.burst int              number of burst requests for rate limiter of controller compound
+      --compound.openstack-designate.ratelimiter.enabled                enables rate limiter for DNS provider requests of controller compound
+      --compound.openstack-designate.ratelimiter.qps int                maximum requests/queries per second of controller compound
+      --compound.ownerids.pool.size int                                 Worker pool size for pool ownerids of controller compound
+      --compound.pool.resync-period duration                            Period for resynchronization of controller compound
+      --compound.pool.size int                                          Worker pool size of controller compound
+      --compound.powerdns.advanced.batch-size int                       batch size for change requests (currently only used for aws-route53) of controller compound
+      --compound.powerdns.advanced.max-retries int                      maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53) of controller compound
+      --compound.powerdns.blocked-zone zone-id                          Blocks a zone given in the format zone-id from a provider as if the zone is not existing. of controller compound
+      --compound.powerdns.ratelimiter.burst int                         number of burst requests for rate limiter of controller compound
+      --compound.powerdns.ratelimiter.enabled                           enables rate limiter for DNS provider requests of controller compound
+      --compound.powerdns.ratelimiter.qps int                           maximum requests/queries per second of controller compound
+      --compound.provider-types string                                  comma separated list of provider types to enable of controller compound
+      --compound.providers.pool.resync-period duration                  Period for resynchronization for pool providers of controller compound
+      --compound.providers.pool.size int                                Worker pool size for pool providers of controller compound
+      --compound.ratelimiter.burst int                                  number of burst requests for rate limiter of controller compound
+      --compound.ratelimiter.enabled                                    enables rate limiter for DNS provider requests of controller compound
+      --compound.ratelimiter.qps int                                    maximum requests/queries per second of controller compound
+      --compound.remote-access-cacert string                            CA who signed client certs file of controller compound
+      --compound.remote-access-client-id string                         identifier used for remote access of controller compound
+      --compound.remote-access-port int                                 port of remote access server for remote-enabled providers of controller compound
+      --compound.remote-access-server-secret-name string                name of secret containing remote access server's certificate of controller compound
+      --compound.remote.advanced.batch-size int                         batch size for change requests (currently only used for aws-route53) of controller compound
+      --compound.remote.advanced.max-retries int                        maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53) of controller compound
+      --compound.remote.blocked-zone zone-id                            Blocks a zone given in the format zone-id from a provider as if the zone is not existing. of controller compound
+      --compound.remote.ratelimiter.burst int                           number of burst requests for rate limiter of controller compound
+      --compound.remote.ratelimiter.enabled                             enables rate limiter for DNS provider requests of controller compound
+      --compound.remote.ratelimiter.qps int                             maximum requests/queries per second of controller compound
+      --compound.reschedule-delay duration                              reschedule delay after losing provider of controller compound
+      --compound.rfc2136.advanced.batch-size int                        batch size for change requests (currently only used for aws-route53) of controller compound
+      --compound.rfc2136.advanced.max-retries int                       maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53) of controller compound
+      --compound.rfc2136.blocked-zone zone-id                           Blocks a zone given in the format zone-id from a provider as if the zone is not existing. of controller compound
+      --compound.rfc2136.ratelimiter.burst int                          number of burst requests for rate limiter of controller compound
+      --compound.rfc2136.ratelimiter.enabled                            enables rate limiter for DNS provider requests of controller compound
+      --compound.rfc2136.ratelimiter.qps int                            maximum requests/queries per second of controller compound
+      --compound.secrets.pool.size int                                  Worker pool size for pool secrets of controller compound
+      --compound.setup int                                              number of processors for controller setup of controller compound
+      --compound.ttl int                                                Default time-to-live for DNS entries. Defines how long the record is kept in cache by DNS servers or resolvers. of controller compound
+      --compound.zonepolicies.pool.size int                             Worker pool size for pool zonepolicies of controller compound
+      --config string                                                   config file
+  -c, --controllers string                                              comma separated list of controllers to start (<name>,<group>,all)
+      --cpuprofile string                                               set file for cpu profiling
+      --default.pool.resync-period duration                             Period for resynchronization for pool default
+      --default.pool.size int                                           Worker pool size for pool default
+      --disable-dnsname-validation                                      disable validation of domain names according to RFC 1123.
+      --disable-namespace-restriction                                   disable access restriction for namespace local access only
+      --disable-zone-state-caching                                      disable use of cached dns zone state on changes
+      --dns-class string                                                identifier used to differentiate responsible controllers for entries, identifier used to differentiate responsible controllers for providers, Class identifier used to differentiate responsible controllers for entry resources
+      --dns-delay duration                                              delay between two dns reconciliations
+      --dns-target-class string                                         identifier used to differentiate responsible dns controllers for target entries, identifier used to differentiate responsible dns controllers for target providers
+      --dns.pool.resync-period duration                                 Period for resynchronization for pool dns
+      --dns.pool.size int                                               Worker pool size for pool dns
+      --dnsentry-source.default.pool.resync-period duration             Period for resynchronization for pool default of controller dnsentry-source
+      --dnsentry-source.default.pool.size int                           Worker pool size for pool default of controller dnsentry-source
+      --dnsentry-source.dns-class string                                identifier used to differentiate responsible controllers for entries of controller dnsentry-source
+      --dnsentry-source.dns-target-class string                         identifier used to differentiate responsible dns controllers for target entries of controller dnsentry-source
+      --dnsentry-source.exclude-domains stringArray                     excluded domains of controller dnsentry-source
+      --dnsentry-source.key string                                      selecting key for annotation of controller dnsentry-source
+      --dnsentry-source.pool.resync-period duration                     Period for resynchronization of controller dnsentry-source
+      --dnsentry-source.pool.size int                                   Worker pool size of controller dnsentry-source
+      --dnsentry-source.target-creator-label-name string                label name to store the creator for generated DNS entries of controller dnsentry-source
+      --dnsentry-source.target-creator-label-value string               label value for creator label of controller dnsentry-source
+      --dnsentry-source.target-name-prefix string                       name prefix in target namespace for cross cluster generation of controller dnsentry-source
+      --dnsentry-source.target-namespace string                         target namespace for cross cluster generation of controller dnsentry-source
+      --dnsentry-source.target-owner-id string                          owner id to use for generated DNS entries of controller dnsentry-source
+      --dnsentry-source.target-owner-object string                      owner object to use for generated DNS entries of controller dnsentry-source
+      --dnsentry-source.target-realms string                            realm(s) to use for generated DNS entries of controller dnsentry-source
+      --dnsentry-source.target-set-ignore-owners                        mark generated DNS entries to omit owner based access control of controller dnsentry-source
+      --dnsentry-source.targets.pool.size int                           Worker pool size for pool targets of controller dnsentry-source
+      --dnsprovider-replication.default.pool.resync-period duration     Period for resynchronization for pool default of controller dnsprovider-replication
+      --dnsprovider-replication.default.pool.size int                   Worker pool size for pool default of controller dnsprovider-replication
+      --dnsprovider-replication.dns-class string                        identifier used to differentiate responsible controllers for providers of controller dnsprovider-replication
+      --dnsprovider-replication.dns-target-class string                 identifier used to differentiate responsible dns controllers for target providers of controller dnsprovider-replication
+      --dnsprovider-replication.pool.resync-period duration             Period for resynchronization of controller dnsprovider-replication
+      --dnsprovider-replication.pool.size int                           Worker pool size of controller dnsprovider-replication
+      --dnsprovider-replication.target-creator-label-name string        label name to store the creator for replicated DNS providers of controller dnsprovider-replication
+      --dnsprovider-replication.target-creator-label-value string       label value for creator label of controller dnsprovider-replication
+      --dnsprovider-replication.target-name-prefix string               name prefix in target namespace for cross cluster replication of controller dnsprovider-replication
+      --dnsprovider-replication.target-namespace string                 target namespace for cross cluster generation of controller dnsprovider-replication
+      --dnsprovider-replication.target-realms string                    realm(s) to use for replicated DNS provider of controller dnsprovider-replication
+      --dnsprovider-replication.targets.pool.size int                   Worker pool size for pool targets of controller dnsprovider-replication
+      --dry-run                                                         just check, don't modify
+      --enable-profiling                                                enables profiling server at path /debug/pprof (needs option --server-port-http)
+      --exclude-domains stringArray                                     excluded domains
+      --force-crd-update                                                enforce update of crds even they are unmanaged
+      --google-clouddns.advanced.batch-size int                         batch size for change requests (currently only used for aws-route53)
+      --google-clouddns.advanced.max-retries int                        maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53)
+      --google-clouddns.blocked-zone zone-id                            Blocks a zone given in the format zone-id from a provider as if the zone is not existing.
+      --google-clouddns.ratelimiter.burst int                           number of burst requests for rate limiter
+      --google-clouddns.ratelimiter.enabled                             enables rate limiter for DNS provider requests
+      --google-clouddns.ratelimiter.qps int                             maximum requests/queries per second
+      --grace-period duration                                           inactivity grace period for detecting end of cleanup for shutdown
+  -h, --help                                                            help for dns-controller-manager
+      --httproutes.pool.size int                                        Worker pool size for pool httproutes
+      --identifier string                                               Identifier used to mark DNS entries in DNS system
+      --infoblox-dns.advanced.batch-size int                            batch size for change requests (currently only used for aws-route53)
+      --infoblox-dns.advanced.max-retries int                           maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53)
+      --infoblox-dns.blocked-zone zone-id                               Blocks a zone given in the format zone-id from a provider as if the zone is not existing.
+      --infoblox-dns.ratelimiter.burst int                              number of burst requests for rate limiter
+      --infoblox-dns.ratelimiter.enabled                                enables rate limiter for DNS provider requests
+      --infoblox-dns.ratelimiter.qps int                                maximum requests/queries per second
+      --ingress-dns.default.pool.resync-period duration                 Period for resynchronization for pool default of controller ingress-dns
+      --ingress-dns.default.pool.size int                               Worker pool size for pool default of controller ingress-dns
+      --ingress-dns.dns-class string                                    identifier used to differentiate responsible controllers for entries of controller ingress-dns
+      --ingress-dns.dns-target-class string                             identifier used to differentiate responsible dns controllers for target entries of controller ingress-dns
+      --ingress-dns.exclude-domains stringArray                         excluded domains of controller ingress-dns
+      --ingress-dns.key string                                          selecting key for annotation of controller ingress-dns
+      --ingress-dns.pool.resync-period duration                         Period for resynchronization of controller ingress-dns
+      --ingress-dns.pool.size int                                       Worker pool size of controller ingress-dns
+      --ingress-dns.target-creator-label-name string                    label name to store the creator for generated DNS entries of controller ingress-dns
+      --ingress-dns.target-creator-label-value string                   label value for creator label of controller ingress-dns
+      --ingress-dns.target-name-prefix string                           name prefix in target namespace for cross cluster generation of controller ingress-dns
+      --ingress-dns.target-namespace string                             target namespace for cross cluster generation of controller ingress-dns
+      --ingress-dns.target-owner-id string                              owner id to use for generated DNS entries of controller ingress-dns
+      --ingress-dns.target-owner-object string                          owner object to use for generated DNS entries of controller ingress-dns
+      --ingress-dns.target-realms string                                realm(s) to use for generated DNS entries of controller ingress-dns
+      --ingress-dns.target-set-ignore-owners                            mark generated DNS entries to omit owner based access control of controller ingress-dns
+      --ingress-dns.targets.pool.size int                               Worker pool size for pool targets of controller ingress-dns
+      --istio-gateways-dns.default.pool.resync-period duration          Period for resynchronization for pool default of controller istio-gateways-dns
+      --istio-gateways-dns.default.pool.size int                        Worker pool size for pool default of controller istio-gateways-dns
+      --istio-gateways-dns.dns-class string                             identifier used to differentiate responsible controllers for entries of controller istio-gateways-dns
+      --istio-gateways-dns.dns-target-class string                      identifier used to differentiate responsible dns controllers for target entries of controller istio-gateways-dns
+      --istio-gateways-dns.exclude-domains stringArray                  excluded domains of controller istio-gateways-dns
+      --istio-gateways-dns.key string                                   selecting key for annotation of controller istio-gateways-dns
+      --istio-gateways-dns.pool.resync-period duration                  Period for resynchronization of controller istio-gateways-dns
+      --istio-gateways-dns.pool.size int                                Worker pool size of controller istio-gateways-dns
+      --istio-gateways-dns.target-creator-label-name string             label name to store the creator for generated DNS entries of controller istio-gateways-dns
+      --istio-gateways-dns.target-creator-label-value string            label value for creator label of controller istio-gateways-dns
+      --istio-gateways-dns.target-name-prefix string                    name prefix in target namespace for cross cluster generation of controller istio-gateways-dns
+      --istio-gateways-dns.target-namespace string                      target namespace for cross cluster generation of controller istio-gateways-dns
+      --istio-gateways-dns.target-owner-id string                       owner id to use for generated DNS entries of controller istio-gateways-dns
+      --istio-gateways-dns.target-owner-object string                   owner object to use for generated DNS entries of controller istio-gateways-dns
+      --istio-gateways-dns.target-realms string                         realm(s) to use for generated DNS entries of controller istio-gateways-dns
+      --istio-gateways-dns.target-set-ignore-owners                     mark generated DNS entries to omit owner based access control of controller istio-gateways-dns
+      --istio-gateways-dns.targets.pool.size int                        Worker pool size for pool targets of controller istio-gateways-dns
+      --istio-gateways-dns.targetsources.pool.size int                  Worker pool size for pool targetsources of controller istio-gateways-dns
+      --istio-gateways-dns.virtualservices.pool.size int                Worker pool size for pool virtualservices of controller istio-gateways-dns
+      --k8s-gateways-dns.default.pool.resync-period duration            Period for resynchronization for pool default of controller k8s-gateways-dns
+      --k8s-gateways-dns.default.pool.size int                          Worker pool size for pool default of controller k8s-gateways-dns
+      --k8s-gateways-dns.dns-class string                               identifier used to differentiate responsible controllers for entries of controller k8s-gateways-dns
+      --k8s-gateways-dns.dns-target-class string                        identifier used to differentiate responsible dns controllers for target entries of controller k8s-gateways-dns
+      --k8s-gateways-dns.exclude-domains stringArray                    excluded domains of controller k8s-gateways-dns
+      --k8s-gateways-dns.httproutes.pool.size int                       Worker pool size for pool httproutes of controller k8s-gateways-dns
+      --k8s-gateways-dns.key string                                     selecting key for annotation of controller k8s-gateways-dns
+      --k8s-gateways-dns.pool.resync-period duration                    Period for resynchronization of controller k8s-gateways-dns
+      --k8s-gateways-dns.pool.size int                                  Worker pool size of controller k8s-gateways-dns
+      --k8s-gateways-dns.target-creator-label-name string               label name to store the creator for generated DNS entries of controller k8s-gateways-dns
+      --k8s-gateways-dns.target-creator-label-value string              label value for creator label of controller k8s-gateways-dns
+      --k8s-gateways-dns.target-name-prefix string                      name prefix in target namespace for cross cluster generation of controller k8s-gateways-dns
+      --k8s-gateways-dns.target-namespace string                        target namespace for cross cluster generation of controller k8s-gateways-dns
+      --k8s-gateways-dns.target-owner-id string                         owner id to use for generated DNS entries of controller k8s-gateways-dns
+      --k8s-gateways-dns.target-owner-object string                     owner object to use for generated DNS entries of controller k8s-gateways-dns
+      --k8s-gateways-dns.target-realms string                           realm(s) to use for generated DNS entries of controller k8s-gateways-dns
+      --k8s-gateways-dns.target-set-ignore-owners                       mark generated DNS entries to omit owner based access control of controller k8s-gateways-dns
+      --k8s-gateways-dns.targets.pool.size int                          Worker pool size for pool targets of controller k8s-gateways-dns
+      --key string                                                      selecting key for annotation
+      --kubeconfig string                                               default cluster access
+      --kubeconfig.conditional-deploy-crds                              deployment of required crds for cluster default only if there is no managed resource in garden namespace deploying it
+      --kubeconfig.disable-deploy-crds                                  disable deployment of required crds for cluster default
+      --kubeconfig.id string                                            id for cluster default
+      --kubeconfig.migration-ids string                                 migration id for cluster default
+      --lease-duration duration                                         lease duration
+      --lease-name string                                               name for lease object
+      --lease-renew-deadline duration                                   lease renew deadline
+      --lease-resource-lock string                                      determines which resource lock to use for leader election, defaults to 'leases'
+      --lease-retry-period duration                                     lease retry period
+      --lock-status-check-period duration                               interval for dns lock status checks
+  -D, --log-level string                                                logrus log level
+      --maintainer string                                               maintainer key for crds (default "dns-controller-manager")
+      --max-metadata-record-deletions-per-reconciliation int            maximum number of metadata owner records that can be deleted per zone reconciliation
+      --name string                                                     name used for controller manager (default "dns-controller-manager")
+      --namespace string                                                namespace for lease (default "kube-system")
+  -n, --namespace-local-access-only                                     enable access restriction for namespace local access only (deprecated)
+      --netlify-dns.advanced.batch-size int                             batch size for change requests (currently only used for aws-route53)
+      --netlify-dns.advanced.max-retries int                            maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53)
+      --netlify-dns.blocked-zone zone-id                                Blocks a zone given in the format zone-id from a provider as if the zone is not existing.
+      --netlify-dns.ratelimiter.burst int                               number of burst requests for rate limiter
+      --netlify-dns.ratelimiter.enabled                                 enables rate limiter for DNS provider requests
+      --netlify-dns.ratelimiter.qps int                                 maximum requests/queries per second
+      --omit-lease                                                      omit lease for development
+      --openstack-designate.advanced.batch-size int                     batch size for change requests (currently only used for aws-route53)
+      --openstack-designate.advanced.max-retries int                    maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53)
+      --openstack-designate.blocked-zone zone-id                        Blocks a zone given in the format zone-id from a provider as if the zone is not existing.
+      --openstack-designate.ratelimiter.burst int                       number of burst requests for rate limiter
+      --openstack-designate.ratelimiter.enabled                         enables rate limiter for DNS provider requests
+      --openstack-designate.ratelimiter.qps int                         maximum requests/queries per second
+      --ownerids.pool.size int                                          Worker pool size for pool ownerids
+      --plugin-file string                                              directory containing go plugins
+      --pool.resync-period duration                                     Period for resynchronization
+      --pool.size int                                                   Worker pool size
+      --powerdns.advanced.batch-size int                                batch size for change requests (currently only used for aws-route53)
+      --powerdns.advanced.max-retries int                               maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53)
+      --powerdns.blocked-zone zone-id                                   Blocks a zone given in the format zone-id from a provider as if the zone is not existing.
+      --powerdns.ratelimiter.burst int                                  number of burst requests for rate limiter
+      --powerdns.ratelimiter.enabled                                    enables rate limiter for DNS provider requests
+      --powerdns.ratelimiter.qps int                                    maximum requests/queries per second
+      --provider-types string                                           comma separated list of provider types to enable
+      --providers string                                                cluster to look for provider objects
+      --providers.conditional-deploy-crds                               deployment of required crds for cluster provider only if there is no managed resource in garden namespace deploying it
+      --providers.disable-deploy-crds                                   disable deployment of required crds for cluster provider
+      --providers.id string                                             id for cluster provider
+      --providers.migration-ids string                                  migration id for cluster provider
+      --providers.pool.resync-period duration                           Period for resynchronization for pool providers
+      --providers.pool.size int                                         Worker pool size for pool providers
+      --ratelimiter.burst int                                           number of burst requests for rate limiter
+      --ratelimiter.enabled                                             enables rate limiter for DNS provider requests
+      --ratelimiter.qps int                                             maximum requests/queries per second
+      --remote-access-cacert string                                     CA who signed client certs file
+      --remote-access-client-id string                                  identifier used for remote access
+      --remote-access-port int                                          port of remote access server for remote-enabled providers
+      --remote-access-server-secret-name string                         name of secret containing remote access server's certificate
+      --remote.advanced.batch-size int                                  batch size for change requests (currently only used for aws-route53)
+      --remote.advanced.max-retries int                                 maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53)
+      --remote.blocked-zone zone-id                                     Blocks a zone given in the format zone-id from a provider as if the zone is not existing.
+      --remote.ratelimiter.burst int                                    number of burst requests for rate limiter
+      --remote.ratelimiter.enabled                                      enables rate limiter for DNS provider requests
+      --remote.ratelimiter.qps int                                      maximum requests/queries per second
+      --reschedule-delay duration                                       reschedule delay after losing provider
+      --rfc2136.advanced.batch-size int                                 batch size for change requests (currently only used for aws-route53)
+      --rfc2136.advanced.max-retries int                                maximum number of retries to avoid paging stops on throttling (currently only used for aws-route53)
+      --rfc2136.blocked-zone zone-id                                    Blocks a zone given in the format zone-id from a provider as if the zone is not existing.
+      --rfc2136.ratelimiter.burst int                                   number of burst requests for rate limiter
+      --rfc2136.ratelimiter.enabled                                     enables rate limiter for DNS provider requests
+      --rfc2136.ratelimiter.qps int                                     maximum requests/queries per second
+      --secrets.pool.size int                                           Worker pool size for pool secrets
+      --server-port-http int                                            HTTP server port (serving /healthz, /metrics, ...)
+      --service-dns.default.pool.resync-period duration                 Period for resynchronization for pool default of controller service-dns
+      --service-dns.default.pool.size int                               Worker pool size for pool default of controller service-dns
+      --service-dns.dns-class string                                    identifier used to differentiate responsible controllers for entries of controller service-dns
+      --service-dns.dns-target-class string                             identifier used to differentiate responsible dns controllers for target entries of controller service-dns
+      --service-dns.exclude-domains stringArray                         excluded domains of controller service-dns
+      --service-dns.key string                                          selecting key for annotation of controller service-dns
+      --service-dns.pool.resync-period duration                         Period for resynchronization of controller service-dns
+      --service-dns.pool.size int                                       Worker pool size of controller service-dns
+      --service-dns.target-creator-label-name string                    label name to store the creator for generated DNS entries of controller service-dns
+      --service-dns.target-creator-label-value string                   label value for creator label of controller service-dns
+      --service-dns.target-name-prefix string                           name prefix in target namespace for cross cluster generation of controller service-dns
+      --service-dns.target-namespace string                             target namespace for cross cluster generation of controller service-dns
+      --service-dns.target-owner-id string                              owner id to use for generated DNS entries of controller service-dns
+      --service-dns.target-owner-object string                          owner object to use for generated DNS entries of controller service-dns
+      --service-dns.target-realms string                                realm(s) to use for generated DNS entries of controller service-dns
+      --service-dns.target-set-ignore-owners                            mark generated DNS entries to omit owner based access control of controller service-dns
+      --service-dns.targets.pool.size int                               Worker pool size for pool targets of controller service-dns
+      --setup int                                                       number of processors for controller setup
+      --target string                                                   target cluster for dns requests
+      --target-creator-label-name string                                label name to store the creator for generated DNS entries, label name to store the creator for replicated DNS providers
+      --target-creator-label-value string                               label value for creator label
+      --target-name-prefix string                                       name prefix in target namespace for cross cluster generation, name prefix in target namespace for cross cluster replication
+      --target-namespace string                                         target namespace for cross cluster generation
+      --target-owner-id string                                          owner id to use for generated DNS entries
+      --target-owner-object string                                      owner object to use for generated DNS entries
+      --target-realms string                                            realm(s) to use for generated DNS entries, realm(s) to use for replicated DNS provider
+      --target-set-ignore-owners                                        mark generated DNS entries to omit owner based access control
+      --target.conditional-deploy-crds                                  deployment of required crds for cluster target only if there is no managed resource in garden namespace deploying it
+      --target.disable-deploy-crds                                      disable deployment of required crds for cluster target
+      --target.id string                                                id for cluster target
+      --target.migration-ids string                                     migration id for cluster target
+      --targets.pool.size int                                           Worker pool size for pool targets
+      --targetsources.pool.size int                                     Worker pool size for pool targetsources
+      --ttl int                                                         Default time-to-live for DNS entries. Defines how long the record is kept in cache by DNS servers or resolvers.
+  -v, --version                                                         version for dns-controller-manager
+      --virtualservices.pool.size int                                   Worker pool size for pool virtualservices
+      --watch-gateways-crds.default.pool.size int                       Worker pool size for pool default of controller watch-gateways-crds
+      --watch-gateways-crds.pool.size int                               Worker pool size of controller watch-gateways-crds
+      --zonepolicies.pool.size int                                      Worker pool size for pool zonepolicies
 ```
 
 ## Extensions
diff --git a/pkg/controller/provider/alicloud/handler.go b/pkg/controller/provider/alicloud/handler.go
index 87aa8d28..27b07fad 100644
--- a/pkg/controller/provider/alicloud/handler.go
+++ b/pkg/controller/provider/alicloud/handler.go
@@ -111,10 +111,6 @@ func (h *Handler) getZoneState(zone provider.DNSHostedZone, _ provider.ZoneCache
 	return state, nil
 }
 
-func (h *Handler) ReportZoneStateConflict(zone provider.DNSHostedZone, err error) bool {
-	return h.cache.ReportZoneStateConflict(zone, err)
-}
-
 func (h *Handler) ExecuteRequests(logger logger.LogContext, zone provider.DNSHostedZone, state provider.DNSZoneState, reqs []*provider.ChangeRequest) error {
 	err := raw.ExecuteRequests(logger, &h.config, h.access, zone, state, reqs)
 	h.cache.ApplyRequests(logger, err, zone, reqs)
diff --git a/pkg/controller/provider/aws/handler.go b/pkg/controller/provider/aws/handler.go
index b2f18978..6c15187d 100644
--- a/pkg/controller/provider/aws/handler.go
+++ b/pkg/controller/provider/aws/handler.go
@@ -204,10 +204,6 @@ func (h *Handler) getZoneState(zone provider.DNSHostedZone, _ provider.ZoneCache
 	return provider.NewDNSZoneState(dnssets), nil
 }
 
-func (h *Handler) ReportZoneStateConflict(zone provider.DNSHostedZone, err error) bool {
-	return h.cache.ReportZoneStateConflict(zone, err)
-}
-
 func (h *Handler) ExecuteRequests(logger logger.LogContext, zone provider.DNSHostedZone, state provider.DNSZoneState, reqs []*provider.ChangeRequest) error {
 	ctx := context.Background()
 	err := h.executeRequests(ctx, logger, zone, state, reqs)
diff --git a/pkg/controller/provider/azure-private/handler.go b/pkg/controller/provider/azure-private/handler.go
index f0386e51..a38f1260 100644
--- a/pkg/controller/provider/azure-private/handler.go
+++ b/pkg/controller/provider/azure-private/handler.go
@@ -184,10 +184,6 @@ func (h *Handler) getZoneState(zone provider.DNSHostedZone, _ provider.ZoneCache
 	return provider.NewDNSZoneState(dnssets), nil
 }
 
-func (h *Handler) ReportZoneStateConflict(zone provider.DNSHostedZone, err error) bool {
-	return h.cache.ReportZoneStateConflict(zone, err)
-}
-
 func (h *Handler) ExecuteRequests(logger logger.LogContext, zone provider.DNSHostedZone, state provider.DNSZoneState, reqs []*provider.ChangeRequest) error {
 	err := h.executeRequests(logger, zone, state, reqs)
 	h.cache.ApplyRequests(logger, err, zone, reqs)
diff --git a/pkg/controller/provider/azure/handler.go b/pkg/controller/provider/azure/handler.go
index 5d5169c5..b19d4c59 100644
--- a/pkg/controller/provider/azure/handler.go
+++ b/pkg/controller/provider/azure/handler.go
@@ -184,10 +184,6 @@ func (h *Handler) getZoneState(zone provider.DNSHostedZone, _ provider.ZoneCache
 	return provider.NewDNSZoneState(dnssets), nil
 }
 
-func (h *Handler) ReportZoneStateConflict(zone provider.DNSHostedZone, err error) bool {
-	return h.cache.ReportZoneStateConflict(zone, err)
-}
-
 func (h *Handler) ExecuteRequests(logger logger.LogContext, zone provider.DNSHostedZone, state provider.DNSZoneState, reqs []*provider.ChangeRequest) error {
 	err := h.executeRequests(logger, zone, state, reqs)
 	h.cache.ApplyRequests(logger, err, zone, reqs)
diff --git a/pkg/controller/provider/cloudflare/handler.go b/pkg/controller/provider/cloudflare/handler.go
index b2d0f238..9913c33a 100644
--- a/pkg/controller/provider/cloudflare/handler.go
+++ b/pkg/controller/provider/cloudflare/handler.go
@@ -109,10 +109,6 @@ func (h *Handler) getZoneState(zone provider.DNSHostedZone, _ provider.ZoneCache
 	return state, nil
 }
 
-func (h *Handler) ReportZoneStateConflict(zone provider.DNSHostedZone, err error) bool {
-	return h.cache.ReportZoneStateConflict(zone, err)
-}
-
 func (h *Handler) ExecuteRequests(logger logger.LogContext, zone provider.DNSHostedZone, state provider.DNSZoneState, reqs []*provider.ChangeRequest) error {
 	err := raw.ExecuteRequests(logger, &h.config, h.access, zone, state, reqs)
 	h.cache.ApplyRequests(logger, err, zone, reqs)
diff --git a/pkg/controller/provider/google/handler.go b/pkg/controller/provider/google/handler.go
index b5df03f1..28f7e47f 100644
--- a/pkg/controller/provider/google/handler.go
+++ b/pkg/controller/provider/google/handler.go
@@ -186,10 +186,6 @@ func (h *Handler) getZoneState(zone provider.DNSHostedZone, _ provider.ZoneCache
 	return provider.NewDNSZoneState(dnssets), nil
 }
 
-func (h *Handler) ReportZoneStateConflict(zone provider.DNSHostedZone, err error) bool {
-	return h.cache.ReportZoneStateConflict(zone, err)
-}
-
 func (h *Handler) ExecuteRequests(logger logger.LogContext, zone provider.DNSHostedZone, state provider.DNSZoneState, reqs []*provider.ChangeRequest) error {
 	err := h.executeRequests(logger, zone, state, reqs)
 	h.cache.ApplyRequests(logger, err, zone, reqs)
diff --git a/pkg/controller/provider/mock/handler.go b/pkg/controller/provider/mock/handler.go
index 102d1466..76f9df94 100644
--- a/pkg/controller/provider/mock/handler.go
+++ b/pkg/controller/provider/mock/handler.go
@@ -109,10 +109,6 @@ func (h *Handler) getZoneState(zone provider.DNSHostedZone, _ provider.ZoneCache
 	return h.mock.CloneZoneState(zone)
 }
 
-func (h *Handler) ReportZoneStateConflict(zone provider.DNSHostedZone, err error) bool {
-	return h.cache.ReportZoneStateConflict(zone, err)
-}
-
 func (h *Handler) ExecuteRequests(logger logger.LogContext, zone provider.DNSHostedZone, state provider.DNSZoneState, reqs []*provider.ChangeRequest) error {
 	err := h.executeRequests(logger, zone, state, reqs)
 	h.cache.ApplyRequests(logger, err, zone, reqs)
diff --git a/pkg/controller/provider/netlify/handler.go b/pkg/controller/provider/netlify/handler.go
index 1fb64ed9..67c49ea6 100644
--- a/pkg/controller/provider/netlify/handler.go
+++ b/pkg/controller/provider/netlify/handler.go
@@ -127,10 +127,6 @@ func (h *Handler) getZoneState(zone provider.DNSHostedZone, _ provider.ZoneCache
 	return state, nil
 }
 
-func (h *Handler) ReportZoneStateConflict(zone provider.DNSHostedZone, err error) bool {
-	return h.cache.ReportZoneStateConflict(zone, err)
-}
-
 func (h *Handler) ExecuteRequests(logger logger.LogContext, zone provider.DNSHostedZone, state provider.DNSZoneState, reqs []*provider.ChangeRequest) error {
 	err := raw.ExecuteRequests(logger, &h.config, h.access, zone, state, reqs)
 	h.cache.ApplyRequests(logger, err, zone, reqs)
diff --git a/pkg/controller/provider/openstack/handler.go b/pkg/controller/provider/openstack/handler.go
index 8395b912..72b3ec04 100644
--- a/pkg/controller/provider/openstack/handler.go
+++ b/pkg/controller/provider/openstack/handler.go
@@ -194,10 +194,6 @@ func (h *Handler) getZoneState(zone provider.DNSHostedZone, _ provider.ZoneCache
 	return provider.NewDNSZoneState(dnssets), nil
 }
 
-func (h *Handler) ReportZoneStateConflict(zone provider.DNSHostedZone, err error) bool {
-	return h.cache.ReportZoneStateConflict(zone, err)
-}
-
 // ExecuteRequests applies a given change request to a given hosted zone.
 func (h *Handler) ExecuteRequests(logger logger.LogContext, zone provider.DNSHostedZone, state provider.DNSZoneState, reqs []*provider.ChangeRequest) error {
 	err := h.executeRequests(logger, zone, state, reqs)
diff --git a/pkg/controller/provider/powerdns/handler.go b/pkg/controller/provider/powerdns/handler.go
index 470c923f..7497cf52 100644
--- a/pkg/controller/provider/powerdns/handler.go
+++ b/pkg/controller/provider/powerdns/handler.go
@@ -147,10 +147,6 @@ func (h *Handler) getZoneState(zone provider.DNSHostedZone, _ provider.ZoneCache
 	return provider.NewDNSZoneState(dnssets), nil
 }
 
-func (h *Handler) ReportZoneStateConflict(zone provider.DNSHostedZone, err error) bool {
-	return h.cache.ReportZoneStateConflict(zone, err)
-}
-
 func (h *Handler) ExecuteRequests(logger logger.LogContext, zone provider.DNSHostedZone, state provider.DNSZoneState, reqs []*provider.ChangeRequest) error {
 	err := h.executeRequests(logger, zone, state, reqs)
 	h.cache.ApplyRequests(logger, err, zone, reqs)
diff --git a/pkg/controller/provider/remote/handler.go b/pkg/controller/provider/remote/handler.go
index 7f0da78d..5f15e3fe 100644
--- a/pkg/controller/provider/remote/handler.go
+++ b/pkg/controller/provider/remote/handler.go
@@ -241,10 +241,6 @@ func (h *Handler) getZoneState(zone provider.DNSHostedZone, _ provider.ZoneCache
 	return provider.NewDNSZoneState(dnssets), nil
 }
 
-func (h *Handler) ReportZoneStateConflict(zone provider.DNSHostedZone, err error) bool {
-	return h.cache.ReportZoneStateConflict(zone, err)
-}
-
 func (h *Handler) MapTargets(dnsName string, targets []provider.Target) []provider.Target {
 	if h.isAWSRoute53(dnsName) {
 		return mapping.MapTargets(targets)
diff --git a/pkg/controller/provider/rfc2136/handler.go b/pkg/controller/provider/rfc2136/handler.go
index 9a1d32ee..2fc84f39 100644
--- a/pkg/controller/provider/rfc2136/handler.go
+++ b/pkg/controller/provider/rfc2136/handler.go
@@ -199,10 +199,6 @@ func (h *Handler) getZoneState(zone provider.DNSHostedZone, _ provider.ZoneCache
 	return provider.NewDNSZoneState(dnssets), nil
 }
 
-func (h *Handler) ReportZoneStateConflict(zone provider.DNSHostedZone, err error) bool {
-	return h.cache.ReportZoneStateConflict(zone, err)
-}
-
 func (h *Handler) ExecuteRequests(logger logger.LogContext, zone provider.DNSHostedZone, state provider.DNSZoneState, reqs []*provider.ChangeRequest) error {
 	err := h.executeRequests(logger, zone, state, reqs)
 	h.cache.ApplyRequests(logger, err, zone, reqs)
diff --git a/pkg/dns/dnsset.go b/pkg/dns/dnsset.go
index fce6c92c..3859649f 100644
--- a/pkg/dns/dnsset.go
+++ b/pkg/dns/dnsset.go
@@ -11,9 +11,9 @@ import (
 )
 
 ////////////////////////////////////////////////////////////////////////////////
-// A DNSSet contains record sets for an DNS name. The name is given without
-// trailing dot. If the provider required this dot, it must be removed or addeed
-// whe reading or writing recordsets, respectively.
+// A DNSSet contains record sets for a DNS name. The name is given without
+// trailing dot. If the provider requires this dot, it must be removed or added
+// whe reading or writing record sets, respectively.
 // Supported record set types are:
 // - TXT
 // - CNAME
@@ -30,24 +30,24 @@ type Ownership interface {
 	GetIds() utils.StringSet
 }
 
-func (dnssets DNSSets) AddRecordSetFromProvider(dnsName string, rs *RecordSet) {
-	dnssets.AddRecordSetFromProviderEx(DNSSetName{DNSName: dnsName}, nil, rs)
+func (dnssets DNSSets) AddRecordSetFromProvider(dnsName string, recordSet *RecordSet) {
+	dnssets.AddRecordSetFromProviderEx(DNSSetName{DNSName: dnsName}, nil, recordSet)
 }
 
-func (dnssets DNSSets) AddRecordSetFromProviderEx(setName DNSSetName, policy *RoutingPolicy, rs *RecordSet) {
-	dnssets.AddRecordSet(setName.Normalize(), policy, rs)
+func (dnssets DNSSets) AddRecordSetFromProviderEx(setName DNSSetName, policy *RoutingPolicy, recordSet *RecordSet) {
+	dnssets.AddRecordSet(setName.Normalize(), policy, recordSet)
 }
 
-func (dnssets DNSSets) AddRecordSet(name DNSSetName, policy *RoutingPolicy, rs *RecordSet) {
+func (dnssets DNSSets) AddRecordSet(name DNSSetName, policy *RoutingPolicy, recordSet *RecordSet) {
 	dnsset := dnssets[name]
 	if dnsset == nil {
 		dnsset = NewDNSSet(name, policy)
 		dnssets[name] = dnsset
 	}
-	dnsset.Sets[rs.Type] = rs
-	if rs.Type == RS_CNAME {
-		for i := range rs.Records {
-			rs.Records[i].Value = NormalizeHostname(rs.Records[i].Value)
+	dnsset.Sets[recordSet.Type] = recordSet
+	if recordSet.Type == RS_CNAME {
+		for i := range recordSet.Records {
+			recordSet.Records[i].Value = NormalizeHostname(recordSet.Records[i].Value)
 		}
 	}
 	dnsset.RoutingPolicy = policy
@@ -85,28 +85,28 @@ func (this *DNSSet) Clone() *DNSSet {
 	}
 }
 
-func (this *DNSSet) getAttr(ty string, name string) string {
-	rset := this.Sets[ty]
-	if rset != nil {
-		return rset.GetAttr(name)
+func (this *DNSSet) getAttr(recordType string, name string) string {
+	recordSet := this.Sets[recordType]
+	if recordSet != nil {
+		return recordSet.GetAttr(name)
 	}
 	return ""
 }
 
-func (this *DNSSet) setAttr(ty string, name string, value string) {
-	rset := this.Sets[ty]
-	if rset == nil {
-		rset = newAttrRecordSet(ty, name, value)
-		this.Sets[rset.Type] = rset
+func (this *DNSSet) setAttr(recordType string, name string, value string) {
+	recordSet := this.Sets[recordType]
+	if recordSet == nil {
+		recordSet = newAttrRecordSet(recordType, name, value)
+		this.Sets[recordSet.Type] = recordSet
 	} else {
-		rset.SetAttr(name, value)
+		recordSet.SetAttr(name, value)
 	}
 }
 
-func (this *DNSSet) deleteAttr(ty string, name string) {
-	rset := this.Sets[ty]
-	if rset != nil {
-		rset.DeleteAttr(name)
+func (this *DNSSet) deleteAttr(recordType string, name string) {
+	recordSet := this.Sets[recordType]
+	if recordSet != nil {
+		recordSet.DeleteAttr(name)
 	}
 }
 
@@ -122,12 +122,12 @@ func (this *DNSSet) DeleteTxtAttr(name string) {
 	this.deleteAttr(RS_TXT, name)
 }
 
-func (this *DNSSet) SetRecordSet(rtype string, ttl int64, values ...string) {
+func (this *DNSSet) SetRecordSet(recordType string, ttl int64, values ...string) {
 	records := make([]*Record, len(values))
 	for i, r := range values {
 		records[i] = &Record{Value: r}
 	}
-	this.Sets[rtype] = &RecordSet{Type: rtype, TTL: ttl, IgnoreTTL: false, Records: records}
+	this.Sets[recordType] = &RecordSet{Type: recordType, TTL: ttl, IgnoreTTL: false, Records: records}
 }
 
 func NewDNSSet(name DNSSetName, routingPolicy *RoutingPolicy) *DNSSet {
@@ -140,8 +140,8 @@ func (this *DNSSet) Match(that *DNSSet) bool {
 }
 
 // MatchRecordTypeSubset matches DNSSet equality for given record type subset.
-func (this *DNSSet) MatchRecordTypeSubset(that *DNSSet, rtype string) bool {
-	return this.match(that, &rtype)
+func (this *DNSSet) MatchRecordTypeSubset(that *DNSSet, recordType string) bool {
+	return this.match(that, &recordType)
 }
 
 func (this *DNSSet) match(that *DNSSet, restrictToRecordType *string) bool {
diff --git a/pkg/dns/provider/changemodel.go b/pkg/dns/provider/changemodel.go
index def243da..384c8686 100644
--- a/pkg/dns/provider/changemodel.go
+++ b/pkg/dns/provider/changemodel.go
@@ -383,31 +383,31 @@ func (this *ChangeModel) Exec(apply bool, delete bool, name dns.DNSSetName, upda
 	mod := false
 	if oldset != nil {
 		this.Debugf("found old for entry %q", oldset.Name)
-		for ty, rset := range newset.Sets {
-			curset := oldset.Sets[ty]
+		for recordType, recordSet := range newset.Sets {
+			curset := oldset.Sets[recordType]
 			if curset == nil {
 				if apply {
-					view.addCreateRequest(newset, ty, done)
+					view.addCreateRequest(newset, recordType, done)
 				}
 				mod = true
 			} else {
-				olddns := oldset.Sets[ty]
-				newdns := newset.Sets[ty]
+				olddns := oldset.Sets[recordType]
+				newdns := newset.Sets[recordType]
 				if olddns.Match(newdns) {
-					if !curset.Match(rset) || !reflect.DeepEqual(spec.RoutingPolicy(), oldset.RoutingPolicy) {
+					if !curset.Match(recordSet) || !reflect.DeepEqual(spec.RoutingPolicy(), oldset.RoutingPolicy) {
 						if apply {
-							view.addUpdateRequest(oldset, newset, ty, done)
+							view.addUpdateRequest(oldset, newset, recordType, done)
 						}
 						mod = true
 					} else {
 						if apply {
-							this.Debugf("records type %s up to date for %s", ty, name)
+							this.Debugf("records type %s up to date for %s", recordType, name)
 						}
 					}
 				} else {
 					if apply {
-						view.addCreateRequest(newset, ty, done)
-						view.addDeleteRequest(oldset, ty, this.wrappedDoneHandler(name, nil))
+						view.addCreateRequest(newset, recordType, done)
+						view.addDeleteRequest(oldset, recordType, this.wrappedDoneHandler(name, nil))
 					}
 					mod = true
 				}
diff --git a/pkg/dns/provider/interface.go b/pkg/dns/provider/interface.go
index 10666f9f..6917d979 100644
--- a/pkg/dns/provider/interface.go
+++ b/pkg/dns/provider/interface.go
@@ -195,7 +195,6 @@ type DNSHandler interface {
 	ProviderType() string
 	GetZones() (DNSHostedZones, error)
 	GetZoneState(DNSHostedZone) (DNSZoneState, error)
-	ReportZoneStateConflict(zone DNSHostedZone, err error) bool
 	ExecuteRequests(logger logger.LogContext, zone DNSHostedZone, state DNSZoneState, reqs []*ChangeRequest) error
 	MapTargets(dnsName string, targets []Target) []Target
 	Release()
@@ -255,10 +254,6 @@ type DNSProvider interface {
 
 	AccountHash() string
 	MapTargets(dnsName string, targets []Target) []Target
-
-	// ReportZoneStateConflict is used to report a conflict because of stale data.
-	// It returns true if zone data will be updated and a retry may resolve the conflict
-	ReportZoneStateConflict(zone DNSHostedZone, err error) bool
 }
 
 type DoneHandler interface {
diff --git a/pkg/dns/provider/provider.go b/pkg/dns/provider/provider.go
index b90c75fb..b7d80357 100644
--- a/pkg/dns/provider/provider.go
+++ b/pkg/dns/provider/provider.go
@@ -141,10 +141,6 @@ func (this *DNSAccount) GetZoneState(zone DNSHostedZone) (DNSZoneState, error) {
 	return state, err
 }
 
-func (this *DNSAccount) ReportZoneStateConflict(zone DNSHostedZone, err error) bool {
-	return this.handler.ReportZoneStateConflict(zone, err)
-}
-
 func (this *DNSAccount) ExecuteRequests(logger logger.LogContext, zone DNSHostedZone, state DNSZoneState, reqs []*ChangeRequest) error {
 	return this.handler.ExecuteRequests(logger, zone, state, reqs)
 }
@@ -601,10 +597,6 @@ func (this *dnsProviderVersion) GetZoneState(zone DNSHostedZone) (DNSZoneState,
 	return this.account.GetZoneState(zone)
 }
 
-func (this *dnsProviderVersion) ReportZoneStateConflict(zone DNSHostedZone, err error) bool {
-	return this.account.ReportZoneStateConflict(zone, err)
-}
-
 func (this *dnsProviderVersion) ExecuteRequests(logger logger.LogContext, zone DNSHostedZone, state DNSZoneState, reqs []*ChangeRequest) error {
 	return this.account.ExecuteRequests(logger, zone, state, reqs)
 }
diff --git a/pkg/dns/provider/zonecache.go b/pkg/dns/provider/zonecache.go
index ade12786..e78d1fc6 100644
--- a/pkg/dns/provider/zonecache.go
+++ b/pkg/dns/provider/zonecache.go
@@ -71,7 +71,6 @@ type ZoneCache interface {
 	GetZoneState(zone DNSHostedZone) (DNSZoneState, error)
 	ApplyRequests(logctx logger.LogContext, err error, zone DNSHostedZone, reqs []*ChangeRequest)
 	Release()
-	ReportZoneStateConflict(zone DNSHostedZone, err error) bool
 }
 
 type onlyZonesCache struct {
@@ -133,10 +132,6 @@ func (c *onlyZonesCache) GetZoneState(zone DNSHostedZone) (DNSZoneState, error)
 func (c *onlyZonesCache) ApplyRequests(_ logger.LogContext, _ error, _ DNSHostedZone, _ []*ChangeRequest) {
 }
 
-func (c *onlyZonesCache) ReportZoneStateConflict(_ DNSHostedZone, _ error) bool {
-	return false
-}
-
 func (c *onlyZonesCache) Release() {
 }
 
@@ -164,10 +159,6 @@ func (c *defaultZoneCache) GetZoneState(zone DNSHostedZone) (DNSZoneState, error
 	return state, err
 }
 
-func (c *defaultZoneCache) ReportZoneStateConflict(zone DNSHostedZone, err error) bool {
-	return c.zoneStates.ReportZoneStateConflict(zone.Id(), err)
-}
-
 func (c *defaultZoneCache) cleanZoneState(zoneID dns.ZoneID) {
 	c.zoneStates.CleanZoneState(zoneID)
 }
@@ -250,10 +241,6 @@ func (s *zoneStates) GetZoneState(zone DNSHostedZone, cache *defaultZoneCache) (
 	return state, true, nil
 }
 
-func (s *zoneStates) ReportZoneStateConflict(_ dns.ZoneID, _ error) bool {
-	return false
-}
-
 func (s *zoneStates) ExecuteRequests(zoneID dns.ZoneID, reqs []*ChangeRequest) {
 	proxy := s.getProxy(zoneID)
 	proxy.lock.Lock()

From b97d940f68d8481a438f38cc4ea8a50f3a364533 Mon Sep 17 00:00:00 2001
From: Martin Weindel <martin.weindel@sap.com>
Date: Wed, 15 Jan 2025 17:19:33 +0100
Subject: [PATCH 07/10] fix check for update recordset

---
 pkg/dns/provider/changemodel.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/pkg/dns/provider/changemodel.go b/pkg/dns/provider/changemodel.go
index 384c8686..f755affd 100644
--- a/pkg/dns/provider/changemodel.go
+++ b/pkg/dns/provider/changemodel.go
@@ -391,9 +391,9 @@ func (this *ChangeModel) Exec(apply bool, delete bool, name dns.DNSSetName, upda
 				}
 				mod = true
 			} else {
-				olddns := oldset.Sets[recordType]
-				newdns := newset.Sets[recordType]
-				if olddns.Match(newdns) {
+				olddns := oldset.Name
+				newdns := newset.Name
+				if olddns == newdns {
 					if !curset.Match(recordSet) || !reflect.DeepEqual(spec.RoutingPolicy(), oldset.RoutingPolicy) {
 						if apply {
 							view.addUpdateRequest(oldset, newset, recordType, done)

From 59b08dc49f67c703b91ca2a87735833828a616fa Mon Sep 17 00:00:00 2001
From: Martin Weindel <martin.weindel@sap.com>
Date: Wed, 15 Jan 2025 17:20:09 +0100
Subject: [PATCH 08/10] add loadtest tool

---
 hack/tools/loadtest/main.go | 122 ++++++++++++++++++++++++++++++++++++
 1 file changed, 122 insertions(+)
 create mode 100644 hack/tools/loadtest/main.go

diff --git a/hack/tools/loadtest/main.go b/hack/tools/loadtest/main.go
new file mode 100644
index 00000000..e2cbab2a
--- /dev/null
+++ b/hack/tools/loadtest/main.go
@@ -0,0 +1,122 @@
+// SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and Gardener contributors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//
+// This small tool can be used to create a given number of DNS entries for load tests:
+//
+// Usage:
+//	go run main.go
+//
+//  Command line options:
+//     -base-domain string
+//     		base domain for the entries (mandatory)
+//     -count int
+//     		number of entries to create (default 10)
+//     -kubeconfig string
+//     		absolute path to the kubeconfig file (defaults to the env variable `KUBECONFIG`)
+//     -label string
+//     		label value for label 'loadtest' to set on the entries (default "true")
+//
+// You may use `kubectl delete dnsentry -l loadtest=<label-value>` to delete them all at once.
+
+package main
+
+import (
+	"context"
+	"flag"
+	"fmt"
+	"os"
+
+	dnsv1alpha1 "github.com/gardener/external-dns-management/pkg/apis/dns/v1alpha1"
+	dnsmanclient "github.com/gardener/external-dns-management/pkg/dnsman2/client"
+	"github.com/gardener/gardener/pkg/controllerutils"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/tools/clientcmd"
+	"k8s.io/utils/ptr"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+var (
+	kubeconfig string
+	baseDomain string
+	labelValue string
+	count      int
+)
+
+func main() {
+	flag.StringVar(&kubeconfig, "kubeconfig", os.Getenv("KUBECONFIG"), "absolute path to the kubeconfig file")
+	flag.IntVar(&count, "count", 10, "number of entries to create")
+	flag.StringVar(&baseDomain, "base-domain", "", "base domain for the entries")
+	flag.StringVar(&labelValue, "label", "true", "label value for label 'loadtest' to set on the entries")
+	flag.Parse()
+
+	if baseDomain == "" {
+		fmt.Fprintf(os.Stderr, "-base-domain is required\n")
+		os.Exit(1)
+	}
+
+	c, err := createClient()
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "failed to create client: %w\n", err)
+		os.Exit(1)
+	}
+
+	ctx := context.Background()
+
+	fmt.Fprintf(os.Stdout, "Creating %d entries - please wait\n", count)
+
+	if err := createEntries(ctx, c, count, "e%05d", baseDomain, "loadtest", labelValue); err != nil {
+		fmt.Fprintf(os.Stderr, "failed to create entries: %w\n", err)
+		os.Exit(2)
+	}
+
+	fmt.Fprintf(os.Stdout, "Done - all %d entries created\n", count)
+}
+
+func createEntries(ctx context.Context, c client.Client, count int, nameTemplate, baseDomain, labelKey, labelValue string) error {
+	for i := 0; i < count; i++ {
+		entry := &dnsv1alpha1.DNSEntry{
+			ObjectMeta: metav1.ObjectMeta{
+				Namespace: "default",
+				Name:      fmt.Sprintf(nameTemplate, i),
+			},
+		}
+		if _, err := controllerutils.CreateOrGetAndMergePatch(ctx, c, entry, func() error {
+			entry.Labels = map[string]string{
+				labelKey: labelValue,
+			}
+			entry.Spec = dnsv1alpha1.DNSEntrySpec{
+				DNSName: fmt.Sprintf("%s.%s", fmt.Sprintf(nameTemplate, i), baseDomain),
+				Targets: []string{fmt.Sprintf("2.%d.%d.%d", i>>16, (i&0xff00)>>8, i&0xff)},
+				TTL:     ptr.To[int64](120),
+			}
+			return nil
+		}); err != nil {
+			return fmt.Errorf("failed to create/update entry %s: %w", entry.Name, err)
+		}
+		if i > 0 && i%100 == 0 {
+			fmt.Fprintf(os.Stdout, "%d/%d entries created...\n", i, count)
+		}
+	}
+
+	return nil
+}
+
+func createClient() (client.Client, error) {
+	if kubeconfig == "" {
+		return nil, fmt.Errorf("-kubeconfig or KUBECONFIG env var is required")
+	}
+
+	cfg, err := clientcmd.LoadFromFile(kubeconfig)
+	if err != nil {
+		return nil, err
+	}
+	clientConfig := clientcmd.NewDefaultClientConfig(*cfg, &clientcmd.ConfigOverrides{})
+	restConfig, err := clientConfig.ClientConfig()
+	if err != nil {
+		return nil, err
+	}
+
+	return client.New(restConfig, client.Options{Scheme: dnsmanclient.ClusterScheme})
+}

From 0cf9d3d2eb8ab9969f0f2b92ab2bf0ec016d9368 Mon Sep 17 00:00:00 2001
From: Martin Weindel <martin.weindel@sap.com>
Date: Thu, 16 Jan 2025 13:09:22 +0100
Subject: [PATCH 09/10] filter entries by kind in name

---
 pkg/dns/source/controller.go            |  2 +-
 pkg/dns/source/filterbykindreonciler.go | 91 +++++++++++++++++++++++++
 2 files changed, 92 insertions(+), 1 deletion(-)
 create mode 100644 pkg/dns/source/filterbykindreonciler.go

diff --git a/pkg/dns/source/controller.go b/pkg/dns/source/controller.go
index 153c31a9..8aae4137 100644
--- a/pkg/dns/source/controller.go
+++ b/pkg/dns/source/controller.go
@@ -91,7 +91,7 @@ func DNSSourceController(source DNSSourceType, reconcilerType controller.Reconci
 		Cluster(cluster.DEFAULT). // first one used as MAIN cluster
 		DefaultWorkerPool(2, 120*time.Second).
 		MainResource(gk.Group, gk.Kind).
-		Reconciler(reconcilers.SlaveReconcilerTypeByFunction(SlaveReconcilerType, SlaveAccessSpecCreatorForSource(source)), "entries").
+		Reconciler(reconcilerTypeFilterByKind(gk.Kind, reconcilers.SlaveReconcilerTypeByFunction(SlaveReconcilerType, SlaveAccessSpecCreatorForSource(source))), "entries").
 		Reconciler(OwnerReconciler, "owner").
 		Cluster(TARGET_CLUSTER, cluster.DEFAULT).
 		CustomResourceDefinitions(entryGroupKind).
diff --git a/pkg/dns/source/filterbykindreonciler.go b/pkg/dns/source/filterbykindreonciler.go
new file mode 100644
index 00000000..e10f5e05
--- /dev/null
+++ b/pkg/dns/source/filterbykindreonciler.go
@@ -0,0 +1,91 @@
+// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and Gardener contributors
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package source
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/gardener/controller-manager-library/pkg/controllermanager/controller"
+	"github.com/gardener/controller-manager-library/pkg/controllermanager/controller/reconcile"
+	"github.com/gardener/controller-manager-library/pkg/logger"
+	"github.com/gardener/controller-manager-library/pkg/resources"
+)
+
+func reconcilerTypeFilterByKind(kind string, reconcilerType controller.ReconcilerType) controller.ReconcilerType {
+	return func(c controller.Interface) (reconcile.Interface, error) {
+		reconciler, err := reconcilerType(c)
+		if err != nil {
+			return nil, err
+		}
+		return &filterByKindReconciler{
+			nested:        reconciler,
+			kindSubstring: fmt.Sprintf("-%s-", strings.ToLower(kind)),
+		}, nil
+	}
+}
+
+// filterByKindReconciler is a reconciler that filters out entries by kind.
+type filterByKindReconciler struct {
+	nested        reconcile.Interface
+	kindSubstring string
+}
+
+var _ reconcile.Interface = &filterByKindReconciler{}
+var _ reconcile.StartInterface = &filterByKindReconciler{}
+var _ reconcile.SetupInterface = &filterByKindReconciler{}
+var _ reconcile.CleanupInterface = &filterByKindReconciler{}
+
+func (f filterByKindReconciler) Start() error {
+	if itf, ok := f.nested.(reconcile.StartInterface); ok {
+		return itf.Start()
+	}
+	return nil
+}
+
+func (f filterByKindReconciler) Setup() error {
+	if itf, ok := f.nested.(reconcile.SetupInterface); ok {
+		return itf.Setup()
+	}
+	return nil
+}
+
+func (f filterByKindReconciler) Cleanup() error {
+	if itf, ok := f.nested.(reconcile.CleanupInterface); ok {
+		return itf.Cleanup()
+	}
+	return nil
+}
+
+func (f filterByKindReconciler) Reconcile(logger logger.LogContext, object resources.Object) reconcile.Status {
+	if !f.isRelevantByHeuristic(object.GetName()) {
+		return reconcile.Succeeded(logger)
+	}
+	return f.nested.Reconcile(logger, object)
+}
+
+func (f filterByKindReconciler) Delete(logger logger.LogContext, object resources.Object) reconcile.Status {
+	if !f.isRelevantByHeuristic(object.GetName()) {
+		return reconcile.Succeeded(logger)
+	}
+	return f.nested.Delete(logger, object)
+}
+
+func (f filterByKindReconciler) Deleted(logger logger.LogContext, key resources.ClusterObjectKey) reconcile.Status {
+	if !f.isRelevantByHeuristic(key.Name()) {
+		return reconcile.Succeeded(logger)
+	}
+	return f.nested.Deleted(logger, key)
+}
+
+func (f filterByKindReconciler) Command(logger logger.LogContext, cmd string) reconcile.Status {
+	return f.nested.Command(logger, cmd)
+}
+
+// isRelevantByHeuristic returns true if the entry name contains the kind substring.
+// This is a heuristic which relies on the fact that the SourceReconciler adds the kind to the entry name.
+func (this *filterByKindReconciler) isRelevantByHeuristic(objectName string) bool {
+	return strings.Contains(objectName, this.kindSubstring)
+}

From b0aeb6f90a0025d4e5f5c8079d72fbe95a5d314e Mon Sep 17 00:00:00 2001
From: Martin Weindel <martin.weindel@sap.com>
Date: Fri, 17 Jan 2025 11:15:39 +0100
Subject: [PATCH 10/10] fix deletion of entries after controller restart

---
 pkg/dns/provider/changemodel.go | 41 +++++++++++++++++++--------------
 pkg/dns/provider/state_zone.go  | 24 +++++++++++++++----
 2 files changed, 43 insertions(+), 22 deletions(-)

diff --git a/pkg/dns/provider/changemodel.go b/pkg/dns/provider/changemodel.go
index f755affd..145df2a3 100644
--- a/pkg/dns/provider/changemodel.go
+++ b/pkg/dns/provider/changemodel.go
@@ -228,15 +228,16 @@ type TargetSpec = dnsutils.TargetSpec
 
 type ChangeModel struct {
 	logger.LogContext
-	config         Config
-	ownership      dns.Ownership
-	context        *zoneReconciliation
-	applied        map[dns.DNSSetName]*dns.DNSSet
-	dangling       *ChangeGroup
-	providergroups map[string]*ChangeGroup
-	zonestate      DNSZoneState
-	failedDNSNames dns.DNSNameSet
-	oldDNSSets     dns.DNSSets
+	config            Config
+	ownership         dns.Ownership
+	context           *zoneReconciliation
+	applied           map[dns.DNSSetName]*dns.DNSSet
+	dangling          *ChangeGroup
+	providergroups    map[string]*ChangeGroup
+	zonestate         DNSZoneState
+	succeededDNSNames dns.DNSNameSet
+	failedDNSNames    dns.DNSNameSet
+	oldDNSSets        dns.DNSSets
 }
 
 type ChangeResult struct {
@@ -247,14 +248,15 @@ type ChangeResult struct {
 
 func NewChangeModel(logger logger.LogContext, ownership dns.Ownership, req *zoneReconciliation, config Config, oldDNSSets dns.DNSSets) *ChangeModel {
 	return &ChangeModel{
-		LogContext:     logger,
-		config:         config,
-		ownership:      ownership,
-		context:        req,
-		applied:        map[dns.DNSSetName]*dns.DNSSet{},
-		providergroups: map[string]*ChangeGroup{},
-		failedDNSNames: dns.DNSNameSet{},
-		oldDNSSets:     oldDNSSets,
+		LogContext:        logger,
+		config:            config,
+		ownership:         ownership,
+		context:           req,
+		applied:           map[dns.DNSSetName]*dns.DNSSet{},
+		providergroups:    map[string]*ChangeGroup{},
+		succeededDNSNames: dns.DNSNameSet{},
+		failedDNSNames:    dns.DNSNameSet{},
+		oldDNSSets:        oldDNSSets,
 	}
 }
 
@@ -469,6 +471,10 @@ func (this *ChangeModel) IsFailed(name dns.DNSSetName) bool {
 	return this.failedDNSNames.Contains(name)
 }
 
+func (this *ChangeModel) IsSucceeded(name dns.DNSSetName) bool {
+	return this.succeededDNSNames.Contains(name)
+}
+
 func (this *ChangeModel) wrappedDoneHandler(name dns.DNSSetName, done DoneHandler) DoneHandler {
 	return &changeModelDoneHandler{
 		changeModel: this,
@@ -500,6 +506,7 @@ func (this *changeModelDoneHandler) Failed(err error) {
 }
 
 func (this *changeModelDoneHandler) Succeeded() {
+	this.changeModel.succeededDNSNames.Add(this.dnsSetName)
 	if this.inner != nil {
 		this.inner.Succeeded()
 	}
diff --git a/pkg/dns/provider/state_zone.go b/pkg/dns/provider/state_zone.go
index c7eb4e6c..373b5fa5 100644
--- a/pkg/dns/provider/state_zone.go
+++ b/pkg/dns/provider/state_zone.go
@@ -208,16 +208,19 @@ func (this *state) reconcileZone(logger logger.LogContext, req *zoneReconciliati
 	for _, e := range outdatedEntries {
 		if changes.IsFailed(e.DNSSetName()) {
 			if oldSet, ok := oldDNSSets[e.DNSSetName()]; ok {
-				if txn := this.getActiveZoneTransaction(zoneid); txn != nil {
-					txn.AddEntryChange(e.ObjectKey(), e.Object().GetGeneration(), oldSet, nil)
-				} else {
-					logger.Warnf("cleanup postpone failure: missing zone for %s", e.ObjectName())
-				}
+				this.addDeleteToNextTransaction(logger, req, zoneid, e, oldSet)
 			} else {
 				logger.Warnf("cleanup postpone failure: old set not found for %s", e.ObjectName())
 			}
 			continue
 		}
+		if !changes.IsSucceeded(e.DNSSetName()) {
+			// DNSEntry in deleting state, but not completely handled (e.g. after restart before zone reconciliation was running)
+			if oldSet, ok := changes.zonestate.GetDNSSets()[e.DNSSetName()]; ok {
+				this.addDeleteToNextTransaction(logger, req, zoneid, e, oldSet)
+				continue
+			}
+		}
 		logger.Infof("cleanup outdated entry %q", e.ObjectName())
 		err := e.RemoveFinalizer()
 		if err == nil || errors.IsNotFound(err) {
@@ -233,6 +236,17 @@ func (this *state) reconcileZone(logger logger.LogContext, req *zoneReconciliati
 	return err
 }
 
+func (this *state) addDeleteToNextTransaction(logger logger.LogContext, req *zoneReconciliation, zoneid dns.ZoneID, e *Entry, oldSet *dns.DNSSet) {
+	if txn := this.getActiveZoneTransaction(zoneid); txn != nil {
+		txn.AddEntryChange(e.ObjectKey(), e.Object().GetGeneration(), oldSet, nil)
+		if req.zone.nextTrigger == 0 {
+			req.zone.nextTrigger = this.config.Delay
+		}
+	} else {
+		logger.Warnf("cleanup postpone failure: missing zone for %s", e.ObjectName())
+	}
+}
+
 func (this *state) deleteZone(zoneid dns.ZoneID) {
 	metrics.DeleteZone(zoneid)
 	delete(this.zones, zoneid)