external-dns/plan/metrics_test.go
Ivan Ka ede1c09086
feat(metrics): added skipped_records_owner_mismatch_per_sync to track ownership conflicts (#6121)
* feat(metrics): added skipped_records_owner_mismatch_total

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_total

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_total

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_total

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_per_sync to track

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_per_sync to track

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_total

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_total

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_per_sync to track

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_per_sync to track

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_per_sync to track

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_per_sync to track

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_per_sync to track

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_per_sync to track

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_per_sync to track

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_per_sync to track

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_per_sync to track

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_per_sync to track

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_per_sync to track

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_per_sync to track

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_per_sync to track

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_per_sync to track

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_per_sync to track

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_per_sync to track

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_per_sync to track

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_per_sync to track

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_total

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_total

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_total

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_total

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_per_sync to track ownership conflicts

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_per_sync to track ownership conflicts

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_per_sync to track ownership conflicts

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(metrics): added skipped_records_owner_mismatch_per_sync to track ownership conflicts

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

---------

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>
2026-03-18 20:32:33 +05:30

215 lines
6.2 KiB
Go

/*
Copyright 2026 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package plan
import (
"testing"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/external-dns/internal/testutils"
"sigs.k8s.io/external-dns/endpoint"
logtest "sigs.k8s.io/external-dns/internal/testutils/log"
)
func TestOwnerMismatchMetric(t *testing.T) {
currentA := &endpoint.Endpoint{
DNSName: "example.domain.com",
Targets: endpoint.Targets{"1.2.3.4"},
RecordType: endpoint.RecordTypeA,
Labels: map[string]string{
endpoint.OwnerLabelKey: "other-owner",
},
}
desiredCname := &endpoint.Endpoint{
DNSName: "example.domain.com",
Targets: endpoint.Targets{"target.example.com"},
RecordType: endpoint.RecordTypeCNAME,
Labels: map[string]string{
endpoint.OwnerLabelKey: "my-owner",
},
}
p := &Plan{
Policies: []Policy{&SyncPolicy{}},
Current: []*endpoint.Endpoint{currentA},
Desired: []*endpoint.Endpoint{desiredCname},
ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
OwnerID: "my-owner",
}
changes := p.Calculate().Changes
assert.Empty(t, changes.Create, "expected no creates due to owner mismatch")
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(
t,
1.0,
registryOwnerMismatchPerSync.Gauge,
map[string]string{
"record_type": endpoint.RecordTypeA,
"foreign_owner": "other-owner",
"domain": "domain.com",
},
)
}
// TestCalculateOwnerMismatchDetection verifies that owner mismatch is detected
// when desired endpoints want to create new record types on DNS names
// that have current records owned by a different owner.
func TestCalculateOwnerMismatchDetection(t *testing.T) {
current := testutils.GenerateTestEndpointsWithDistribution(
map[string]int{endpoint.RecordTypeA: 10},
map[string]int{"example.com": 1},
map[string]int{"other-owner": 1},
)
// Create desired endpoints: same DNS names but with different type records (new type triggers Create)
var desired []*endpoint.Endpoint
for _, ep := range current {
desired = append(desired, &endpoint.Endpoint{
DNSName: ep.DNSName,
Targets: endpoint.Targets{"abrakadabra"},
RecordType: endpoint.RecordTypeTXT,
RecordTTL: 300,
})
}
p := &Plan{
Policies: []Policy{&SyncPolicy{}},
Current: current,
Desired: desired,
ManagedRecords: endpoint.KnownRecordTypes,
OwnerID: "my-owner",
}
hook := logtest.LogsUnderTestWithLogLevel(log.DebugLevel, t)
changes := p.Calculate().Changes
assert.Empty(t, changes.Create, "expected no creates due to owner mismatch")
logtest.TestHelperLogContains("owner id does not match for one or more items to create", hook, t)
}
func TestOwnerMismatchMetricDistribution(t *testing.T) {
p := newOwnerMismatchFixture()
p.Calculate()
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 44, registryOwnerMismatchPerSync.Gauge,
map[string]string{"record_type": endpoint.RecordTypeSRV})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 41, registryOwnerMismatchPerSync.Gauge,
map[string]string{"foreign_owner": "owner1"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 125, registryOwnerMismatchPerSync.Gauge,
map[string]string{"owner": "my-owner"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 21, registryOwnerMismatchPerSync.Gauge,
map[string]string{"foreign_owner": "owner1", "domain": "open.net"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 2, registryOwnerMismatchPerSync.Gauge,
map[string]string{"record_type": endpoint.RecordTypeCNAME, "foreign_owner": "owner1", "domain": "open.net"})
}
func BenchmarkOwnerMismatchMetricDistribution(b *testing.B) {
p := newOwnerMismatchFixture(1000)
for b.Loop() {
p.Calculate()
}
}
func newOwnerMismatchFixture(scale ...int) *Plan {
factor := 1
if len(scale) > 0 && scale[0] > 1 {
factor = scale[0]
}
current := testutils.GenerateTestEndpointsWithDistribution(
map[string]int{
endpoint.RecordTypeA: 12 * factor,
endpoint.RecordTypeAAAA: 27 * factor,
endpoint.RecordTypeCNAME: 42 * factor,
endpoint.RecordTypeSRV: 44 * factor,
},
map[string]int{
"example.com": 1,
"tld.org": 2,
"open.net": 3,
},
map[string]int{"owner1": 1, "owner2": 1, "owner3": 1},
)
var desired []*endpoint.Endpoint
for _, ep := range current {
desired = append(desired, &endpoint.Endpoint{
DNSName: ep.DNSName,
Targets: endpoint.Targets{"txt-value"},
RecordType: endpoint.RecordTypeTXT,
RecordTTL: 300,
})
}
return &Plan{
Policies: []Policy{&SyncPolicy{}},
Current: current,
Desired: desired,
ManagedRecords: endpoint.KnownRecordTypes,
OwnerID: "my-owner",
}
}
func TestFlushOwnerMismatch(t *testing.T) {
tests := []struct {
name string
owner string
current *endpoint.Endpoint
calls int
expected float64
expectedTags map[string]string
}{
{
name: "handles_missing_foreign_owner_label",
owner: "me",
current: &endpoint.Endpoint{
DNSName: "sub.domain.net",
RecordType: endpoint.RecordTypeTXT,
Labels: map[string]string{},
},
calls: 1,
expected: 1.0,
expectedTags: map[string]string{
"record_type": endpoint.RecordTypeTXT,
"owner": "me",
"foreign_owner": "",
"domain": "domain.net",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
registryOwnerMismatchPerSync.Gauge.Reset()
for range tt.calls {
recordOwnerMismatch(tt.owner, tt.current)
}
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(
t,
tt.expected,
registryOwnerMismatchPerSync.Gauge,
tt.expectedTags,
)
})
}
}