mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-06 17:46:57 +02:00
Add AAAA records metrics
This commit is contained in:
parent
6b20ba301c
commit
2eed9cb6ba
@ -102,6 +102,14 @@ var (
|
||||
Help: "Number of Registry A records.",
|
||||
},
|
||||
)
|
||||
registryAAAARecords = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Namespace: "external_dns",
|
||||
Subsystem: "registry",
|
||||
Name: "aaaa_records",
|
||||
Help: "Number of Registry AAAA records.",
|
||||
},
|
||||
)
|
||||
sourceARecords = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Namespace: "external_dns",
|
||||
@ -110,6 +118,14 @@ var (
|
||||
Help: "Number of Source A records.",
|
||||
},
|
||||
)
|
||||
sourceAAAARecords = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Namespace: "external_dns",
|
||||
Subsystem: "source",
|
||||
Name: "aaaa_records",
|
||||
Help: "Number of Source AAAA records.",
|
||||
},
|
||||
)
|
||||
verifiedARecords = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Namespace: "external_dns",
|
||||
@ -118,6 +134,14 @@ var (
|
||||
Help: "Number of DNS A-records that exists both in source and registry.",
|
||||
},
|
||||
)
|
||||
verifiedAAAARecords = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Namespace: "external_dns",
|
||||
Subsystem: "controller",
|
||||
Name: "verified_aaaa_records",
|
||||
Help: "Number of DNS AAAA-records that exists both in source and registry.",
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -130,8 +154,11 @@ func init() {
|
||||
prometheus.MustRegister(deprecatedSourceErrors)
|
||||
prometheus.MustRegister(controllerNoChangesTotal)
|
||||
prometheus.MustRegister(registryARecords)
|
||||
prometheus.MustRegister(registryAAAARecords)
|
||||
prometheus.MustRegister(sourceARecords)
|
||||
prometheus.MustRegister(sourceAAAARecords)
|
||||
prometheus.MustRegister(verifiedARecords)
|
||||
prometheus.MustRegister(verifiedAAAARecords)
|
||||
}
|
||||
|
||||
// Controller is responsible for orchestrating the different components.
|
||||
@ -171,8 +198,9 @@ func (c *Controller) RunOnce(ctx context.Context) error {
|
||||
missingRecords := c.Registry.MissingRecords()
|
||||
|
||||
registryEndpointsTotal.Set(float64(len(records)))
|
||||
regARecords := filterARecords(records)
|
||||
registryARecords.Set(float64(len(regARecords)))
|
||||
regARecords, regAAAARecords := countAddressRecords(records)
|
||||
registryARecords.Set(float64(regARecords))
|
||||
registryAAAARecords.Set(float64(regAAAARecords))
|
||||
ctx = context.WithValue(ctx, provider.RecordsContextKey, records)
|
||||
|
||||
endpoints, err := c.Source.Endpoints(ctx)
|
||||
@ -182,10 +210,12 @@ func (c *Controller) RunOnce(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
sourceEndpointsTotal.Set(float64(len(endpoints)))
|
||||
srcARecords := filterARecords(endpoints)
|
||||
sourceARecords.Set(float64(len(srcARecords)))
|
||||
vRecords := fetchMatchingARecords(endpoints, records)
|
||||
verifiedARecords.Set(float64(len(vRecords)))
|
||||
srcARecords, srcAAAARecords := countAddressRecords(endpoints)
|
||||
sourceARecords.Set(float64(srcARecords))
|
||||
sourceAAAARecords.Set(float64(srcAAAARecords))
|
||||
vARecords, vAAAARecords := countMatchingAddressRecords(endpoints, records)
|
||||
verifiedARecords.Set(float64(vARecords))
|
||||
verifiedAAAARecords.Set(float64(vAAAARecords))
|
||||
endpoints = c.Registry.AdjustEndpoints(endpoints)
|
||||
|
||||
if len(missingRecords) > 0 {
|
||||
@ -238,30 +268,44 @@ func (c *Controller) RunOnce(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checks and returns the intersection of A records in endpoint and registry.
|
||||
func fetchMatchingARecords(endpoints []*endpoint.Endpoint, registryRecords []*endpoint.Endpoint) []string {
|
||||
aRecords := filterARecords(endpoints)
|
||||
recordsMap := make(map[string]struct{})
|
||||
// Counts the intersections of A and AAAA records in endpoint and registry.
|
||||
func countMatchingAddressRecords(endpoints []*endpoint.Endpoint, registryRecords []*endpoint.Endpoint) (int, int) {
|
||||
recordsMap := make(map[string]map[string]struct{})
|
||||
for _, regRecord := range registryRecords {
|
||||
recordsMap[regRecord.DNSName] = struct{}{}
|
||||
if _, found := recordsMap[regRecord.DNSName]; !found {
|
||||
recordsMap[regRecord.DNSName] = make(map[string]struct{})
|
||||
}
|
||||
recordsMap[regRecord.DNSName][regRecord.RecordType] = struct{}{}
|
||||
}
|
||||
var cm []string
|
||||
for _, sourceRecord := range aRecords {
|
||||
if _, found := recordsMap[sourceRecord]; found {
|
||||
cm = append(cm, sourceRecord)
|
||||
aCount := 0
|
||||
aaaaCount := 0
|
||||
for _, sourceRecord := range endpoints {
|
||||
if _, found := recordsMap[sourceRecord.DNSName]; found {
|
||||
if _, found := recordsMap[sourceRecord.DNSName][sourceRecord.RecordType]; found {
|
||||
switch sourceRecord.RecordType {
|
||||
case endpoint.RecordTypeA:
|
||||
aCount++
|
||||
case endpoint.RecordTypeAAAA:
|
||||
aaaaCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return cm
|
||||
return aCount, aaaaCount
|
||||
}
|
||||
|
||||
func filterARecords(endpoints []*endpoint.Endpoint) []string {
|
||||
var aRecords []string
|
||||
func countAddressRecords(endpoints []*endpoint.Endpoint) (int, int) {
|
||||
aCount := 0
|
||||
aaaaCount := 0
|
||||
for _, endPoint := range endpoints {
|
||||
if endPoint.RecordType == endpoint.RecordTypeA {
|
||||
aRecords = append(aRecords, endPoint.DNSName)
|
||||
switch endPoint.RecordType {
|
||||
case endpoint.RecordTypeA:
|
||||
aCount++
|
||||
case endpoint.RecordTypeAAAA:
|
||||
aaaaCount++
|
||||
}
|
||||
}
|
||||
return aRecords
|
||||
return aCount, aaaaCount
|
||||
}
|
||||
|
||||
// ScheduleRunOnce makes sure execution happens at most once per interval.
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"errors"
|
||||
"math"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -83,32 +84,20 @@ func (p *errorMockProvider) Records(ctx context.Context) ([]*endpoint.Endpoint,
|
||||
|
||||
// ApplyChanges validates that the passed in changes satisfy the assumptions.
|
||||
func (p *mockProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
||||
if len(changes.Create) != len(p.ExpectChanges.Create) {
|
||||
return errors.New("number of created records is wrong")
|
||||
if err := verifyEndpoints(changes.Create, p.ExpectChanges.Create); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range changes.Create {
|
||||
if changes.Create[i].DNSName != p.ExpectChanges.Create[i].DNSName || !changes.Create[i].Targets.Same(p.ExpectChanges.Create[i].Targets) {
|
||||
return errors.New("created record is wrong")
|
||||
}
|
||||
if err := verifyEndpoints(changes.UpdateNew, p.ExpectChanges.UpdateNew); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range changes.UpdateNew {
|
||||
if changes.UpdateNew[i].DNSName != p.ExpectChanges.UpdateNew[i].DNSName || !changes.UpdateNew[i].Targets.Same(p.ExpectChanges.UpdateNew[i].Targets) {
|
||||
return errors.New("delete record is wrong")
|
||||
}
|
||||
if err := verifyEndpoints(changes.UpdateOld, p.ExpectChanges.UpdateOld); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range changes.UpdateOld {
|
||||
if changes.UpdateOld[i].DNSName != p.ExpectChanges.UpdateOld[i].DNSName || !changes.UpdateOld[i].Targets.Same(p.ExpectChanges.UpdateOld[i].Targets) {
|
||||
return errors.New("delete record is wrong")
|
||||
}
|
||||
}
|
||||
|
||||
for i := range changes.Delete {
|
||||
if changes.Delete[i].DNSName != p.ExpectChanges.Delete[i].DNSName || !changes.Delete[i].Targets.Same(p.ExpectChanges.Delete[i].Targets) {
|
||||
return errors.New("delete record is wrong")
|
||||
}
|
||||
if err := verifyEndpoints(changes.Delete, p.ExpectChanges.Delete); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(ctx.Value(provider.RecordsContextKey), p.RecordsStore) {
|
||||
@ -117,6 +106,21 @@ func (p *mockProvider) ApplyChanges(ctx context.Context, changes *plan.Changes)
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifyEndpoints(actual, expected []*endpoint.Endpoint) error {
|
||||
if len(actual) != len(expected) {
|
||||
return errors.New("number of records is wrong")
|
||||
}
|
||||
sort.Slice(actual, func(i, j int) bool {
|
||||
return actual[i].DNSName < actual[j].DNSName
|
||||
})
|
||||
for i := range actual {
|
||||
if actual[i].DNSName != expected[i].DNSName || !actual[i].Targets.Same(expected[i].Targets) {
|
||||
return errors.New("record is wrong")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// newMockProvider creates a new mockProvider returning the given endpoints and validating the desired changes.
|
||||
func newMockProvider(endpoints []*endpoint.Endpoint, changes *plan.Changes) provider.Provider {
|
||||
dnsProvider := &mockProvider{
|
||||
@ -132,7 +136,7 @@ func TestRunOnce(t *testing.T) {
|
||||
// Fake some desired endpoints coming from our source.
|
||||
source := new(testutils.MockSource)
|
||||
cfg := externaldns.NewConfig()
|
||||
cfg.ManagedDNSRecordTypes = []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}
|
||||
cfg.ManagedDNSRecordTypes = []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME}
|
||||
source.On("Endpoints").Return([]*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "create-record",
|
||||
@ -144,6 +148,16 @@ func TestRunOnce(t *testing.T) {
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"8.8.4.4"},
|
||||
},
|
||||
{
|
||||
DNSName: "create-aaaa-record",
|
||||
RecordType: endpoint.RecordTypeAAAA,
|
||||
Targets: endpoint.Targets{"2001:DB8::1"},
|
||||
},
|
||||
{
|
||||
DNSName: "update-aaaa-record",
|
||||
RecordType: endpoint.RecordTypeAAAA,
|
||||
Targets: endpoint.Targets{"2001:DB8::2"},
|
||||
},
|
||||
}, nil)
|
||||
|
||||
// Fake some existing records in our DNS provider and validate some desired changes.
|
||||
@ -159,18 +173,32 @@ func TestRunOnce(t *testing.T) {
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"4.3.2.1"},
|
||||
},
|
||||
{
|
||||
DNSName: "update-aaaa-record",
|
||||
RecordType: endpoint.RecordTypeAAAA,
|
||||
Targets: endpoint.Targets{"2001:DB8::3"},
|
||||
},
|
||||
{
|
||||
DNSName: "delete-aaaa-record",
|
||||
RecordType: endpoint.RecordTypeAAAA,
|
||||
Targets: endpoint.Targets{"2001:DB8::4"},
|
||||
},
|
||||
},
|
||||
&plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
{DNSName: "create-aaaa-record", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1"}},
|
||||
{DNSName: "create-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
},
|
||||
UpdateNew: []*endpoint.Endpoint{
|
||||
{DNSName: "update-aaaa-record", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::2"}},
|
||||
{DNSName: "update-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"8.8.4.4"}},
|
||||
},
|
||||
UpdateOld: []*endpoint.Endpoint{
|
||||
{DNSName: "update-aaaa-record", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::3"}},
|
||||
{DNSName: "update-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"8.8.8.8"}},
|
||||
},
|
||||
Delete: []*endpoint.Endpoint{
|
||||
{DNSName: "delete-aaaa-record", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::4"}},
|
||||
{DNSName: "delete-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"4.3.2.1"}},
|
||||
},
|
||||
},
|
||||
@ -193,6 +221,7 @@ func TestRunOnce(t *testing.T) {
|
||||
source.AssertExpectations(t)
|
||||
// check the verified records
|
||||
assert.Equal(t, math.Float64bits(1), valueFromMetric(verifiedARecords))
|
||||
assert.Equal(t, math.Float64bits(1), valueFromMetric(verifiedAAAARecords))
|
||||
}
|
||||
|
||||
func valueFromMetric(metric prometheus.Gauge) uint64 {
|
||||
@ -253,7 +282,7 @@ func TestShouldRunOnce(t *testing.T) {
|
||||
func testControllerFiltersDomains(t *testing.T, configuredEndpoints []*endpoint.Endpoint, domainFilter endpoint.DomainFilterInterface, providerEndpoints []*endpoint.Endpoint, expectedChanges []*plan.Changes) {
|
||||
t.Helper()
|
||||
cfg := externaldns.NewConfig()
|
||||
cfg.ManagedDNSRecordTypes = []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}
|
||||
cfg.ManagedDNSRecordTypes = []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME}
|
||||
|
||||
source := new(testutils.MockSource)
|
||||
source.On("Endpoints").Return(configuredEndpoints, nil)
|
||||
@ -526,6 +555,85 @@ func TestVerifyARecords(t *testing.T) {
|
||||
}},
|
||||
)
|
||||
assert.Equal(t, math.Float64bits(2), valueFromMetric(verifiedARecords))
|
||||
assert.Equal(t, math.Float64bits(0), valueFromMetric(verifiedAAAARecords))
|
||||
}
|
||||
|
||||
func TestVerifyAAAARecords(t *testing.T) {
|
||||
testControllerFiltersDomains(
|
||||
t,
|
||||
[]*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "create-record.used.tld",
|
||||
RecordType: endpoint.RecordTypeAAAA,
|
||||
Targets: endpoint.Targets{"2001:DB8::1"},
|
||||
},
|
||||
{
|
||||
DNSName: "some-record.used.tld",
|
||||
RecordType: endpoint.RecordTypeAAAA,
|
||||
Targets: endpoint.Targets{"2001:DB8::2"},
|
||||
},
|
||||
},
|
||||
endpoint.NewDomainFilter([]string{"used.tld"}),
|
||||
[]*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "some-record.used.tld",
|
||||
RecordType: endpoint.RecordTypeAAAA,
|
||||
Targets: endpoint.Targets{"2001:DB8::2"},
|
||||
},
|
||||
{
|
||||
DNSName: "create-record.used.tld",
|
||||
RecordType: endpoint.RecordTypeAAAA,
|
||||
Targets: endpoint.Targets{"2001:DB8::1"},
|
||||
},
|
||||
},
|
||||
[]*plan.Changes{},
|
||||
)
|
||||
assert.Equal(t, math.Float64bits(2), valueFromMetric(verifiedAAAARecords))
|
||||
|
||||
testControllerFiltersDomains(
|
||||
t,
|
||||
[]*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "some-record.1.used.tld",
|
||||
RecordType: endpoint.RecordTypeAAAA,
|
||||
Targets: endpoint.Targets{"2001:DB8::1"},
|
||||
},
|
||||
{
|
||||
DNSName: "some-record.2.used.tld",
|
||||
RecordType: endpoint.RecordTypeAAAA,
|
||||
Targets: endpoint.Targets{"2001:DB8::2"},
|
||||
},
|
||||
{
|
||||
DNSName: "some-record.3.used.tld",
|
||||
RecordType: endpoint.RecordTypeAAAA,
|
||||
Targets: endpoint.Targets{"2001:DB8::3"},
|
||||
},
|
||||
},
|
||||
endpoint.NewDomainFilter([]string{"used.tld"}),
|
||||
[]*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "some-record.1.used.tld",
|
||||
RecordType: endpoint.RecordTypeAAAA,
|
||||
Targets: endpoint.Targets{"2001:DB8::1"},
|
||||
},
|
||||
{
|
||||
DNSName: "some-record.2.used.tld",
|
||||
RecordType: endpoint.RecordTypeAAAA,
|
||||
Targets: endpoint.Targets{"2001:DB8::2"},
|
||||
},
|
||||
},
|
||||
[]*plan.Changes{{
|
||||
Create: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "some-record.3.used.tld",
|
||||
RecordType: endpoint.RecordTypeAAAA,
|
||||
Targets: endpoint.Targets{"2001:DB8::3"},
|
||||
},
|
||||
},
|
||||
}},
|
||||
)
|
||||
assert.Equal(t, math.Float64bits(0), valueFromMetric(verifiedARecords))
|
||||
assert.Equal(t, math.Float64bits(2), valueFromMetric(verifiedAAAARecords))
|
||||
}
|
||||
|
||||
func TestARecords(t *testing.T) {
|
||||
@ -628,3 +736,50 @@ func TestMissingRecordsApply(t *testing.T) {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAAAARecords(t *testing.T) {
|
||||
testControllerFiltersDomains(
|
||||
t,
|
||||
[]*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "record1.used.tld",
|
||||
RecordType: endpoint.RecordTypeAAAA,
|
||||
Targets: endpoint.Targets{"2001:DB8::1"},
|
||||
},
|
||||
{
|
||||
DNSName: "record2.used.tld",
|
||||
RecordType: endpoint.RecordTypeAAAA,
|
||||
Targets: endpoint.Targets{"2001:DB8::2"},
|
||||
},
|
||||
{
|
||||
DNSName: "_mysql-svc._tcp.mysql.used.tld",
|
||||
RecordType: endpoint.RecordTypeSRV,
|
||||
Targets: endpoint.Targets{"0 50 30007 mysql.used.tld"},
|
||||
},
|
||||
},
|
||||
endpoint.NewDomainFilter([]string{"used.tld"}),
|
||||
[]*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "record1.used.tld",
|
||||
RecordType: endpoint.RecordTypeAAAA,
|
||||
Targets: endpoint.Targets{"2001:DB8::1"},
|
||||
},
|
||||
{
|
||||
DNSName: "_mysql-svc._tcp.mysql.used.tld",
|
||||
RecordType: endpoint.RecordTypeSRV,
|
||||
Targets: endpoint.Targets{"0 50 30007 mysql.used.tld"},
|
||||
},
|
||||
},
|
||||
[]*plan.Changes{{
|
||||
Create: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "record2.used.tld",
|
||||
RecordType: endpoint.RecordTypeAAAA,
|
||||
Targets: endpoint.Targets{"2001:DB8::2"},
|
||||
},
|
||||
},
|
||||
}},
|
||||
)
|
||||
assert.Equal(t, math.Float64bits(2), valueFromMetric(sourceAAAARecords))
|
||||
assert.Equal(t, math.Float64bits(1), valueFromMetric(registryAAAARecords))
|
||||
}
|
||||
|
20
docs/faq.md
20
docs/faq.md
@ -178,16 +178,18 @@ You can use the host label in the metric to figure out if the request was agains
|
||||
|
||||
Here is the full list of available metrics provided by ExternalDNS:
|
||||
|
||||
| Name | Description | Type |
|
||||
| --------------------------------------------------- | ------------------------------------------------------- | ------- |
|
||||
| external_dns_controller_last_sync_timestamp_seconds | Timestamp of last successful sync with the DNS provider | Gauge |
|
||||
| external_dns_registry_endpoints_total | Number of Endpoints in all sources | Gauge |
|
||||
| external_dns_registry_errors_total | Number of Registry errors | Counter |
|
||||
| external_dns_source_endpoints_total | Number of Endpoints in the registry | Gauge |
|
||||
| external_dns_source_errors_total | Number of Source errors | Counter |
|
||||
| external_dns_controller_verified_records | Number of DNS A-records that exists both in | Gauge |
|
||||
| | source & registry | |
|
||||
| Name | Description | Type |
|
||||
| --------------------------------------------------- | ------------------------------------------------------------------ | ------- |
|
||||
| external_dns_controller_last_sync_timestamp_seconds | Timestamp of last successful sync with the DNS provider | Gauge |
|
||||
| external_dns_registry_endpoints_total | Number of Endpoints in all sources | Gauge |
|
||||
| external_dns_registry_errors_total | Number of Registry errors | Counter |
|
||||
| external_dns_source_endpoints_total | Number of Endpoints in the registry | Gauge |
|
||||
| external_dns_source_errors_total | Number of Source errors | Counter |
|
||||
| external_dns_controller_verified_aaaa_records | Number of DNS AAAA-records that exists both in source and registry | Gauge |
|
||||
| external_dns_controller_verified_a_records | Number of DNS A-records that exists both in source and registry | Gauge |
|
||||
| external_dns_registry_aaaa_records | Number of AAAA records in registry | Gauge |
|
||||
| external_dns_registry_a_records | Number of A records in registry | Gauge |
|
||||
| external_dns_source_aaaa_records | Number of AAAA records in source | Gauge |
|
||||
| external_dns_source_a_records | Number of A records in source | Gauge |
|
||||
|
||||
### How can I run ExternalDNS under a specific GCP Service Account, e.g. to access DNS records in other projects?
|
||||
|
Loading…
Reference in New Issue
Block a user